summaryrefslogtreecommitdiffstatshomepage
path: root/src/tools
diff options
context:
space:
mode:
author Miodrag Milanovic <mmicko@gmail.com>2016-04-03 19:06:06 +0200
committer Miodrag Milanovic <mmicko@gmail.com>2016-04-03 19:06:06 +0200
commitcea094a3a37c9deec4e5d447bb2147267e01a6e5 (patch)
treef41ba2581c9d76e5cbd21c93b4c11402bbe8ded8 /src/tools
parent1aa8a350d3b4e5dedd5ad5d19294570e64a9dc1b (diff)
moved aueffectutil to tools (nw)
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/aueffectutil-Info.plist45
-rw-r--r--src/tools/aueffectutil.mm1053
2 files changed, 1098 insertions, 0 deletions
diff --git a/src/tools/aueffectutil-Info.plist b/src/tools/aueffectutil-Info.plist
new file mode 100644
index 00000000000..e5094157c33
--- /dev/null
+++ b/src/tools/aueffectutil-Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDisplayName</key>
+ <string>AUEffectUtil</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>aueffect</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>AUEffect</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array/>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>NSDocumentClass</key>
+ <string>AUEffectDocument</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>aupreset</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>AudioUnit Preset</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array/>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>NSDocumentClass</key>
+ <string>AUEffectDocument</string>
+ </dict>
+ </array>
+ <key>CFBundleIdentifier</key>
+ <string>org.mamedev.aueffectutil</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>AUEffectUtil</string>
+</dict>
+</plist>
diff --git a/src/tools/aueffectutil.mm b/src/tools/aueffectutil.mm
new file mode 100644
index 00000000000..531d814e987
--- /dev/null
+++ b/src/tools/aueffectutil.mm
@@ -0,0 +1,1053 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+#import <AvailabilityMacros.h>
+#import <AudioUnit/AudioUnit.h>
+#import <AudioUnit/AUCocoaUIView.h>
+#import <AudioToolbox/AudioToolbox.h>
+#import <Cocoa/Cocoa.h>
+#import <CoreAudio/CoreAudio.h>
+#import <CoreAudioKit/CoreAudioKit.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <CoreServices/CoreServices.h>
+
+#include <utility>
+#include <vector>
+
+#include <stdlib.h>
+
+
+#ifdef MAC_OS_X_VERSION_MAX_ALLOWED
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060
+
+typedef ComponentDescription AudioComponentDescription;
+
+@protocol NSApplicationDelegate <NSObject>
+@end
+
+@protocol NSWindowDelegate <NSObject>
+@end
+
+#endif // MAC_OS_X_VERSION_MAX_ALLOWED < 1060
+
+#endif // MAC_OS_X_VERSION_MAX_ALLOWED
+
+
+struct EffectInfo
+{
+ Component component;
+ OSType type;
+ OSType subtype;
+ OSType manufacturer;
+};
+
+
+static NSString *const AUEffectUtilErrorDomain = @"AUEffectUtilErrorDomain";
+
+static NSString *const AUEffectDocumentType = @"AUEffect";
+static NSString *const AUPresetDocumentType = @"AudioUnit Preset";
+
+static NSString *const ComponentTypeKey = @"ComponentType";
+static NSString *const ComponentSubTypeKey = @"ComponentSubType";
+static NSString *const ComponentManufacturerKey = @"ComponentManufacturer";
+static NSString *const ClassInfoKey = @"ClassInfo";
+static NSString *const ForceGenericViewKey = @"ForceGenericView";
+static NSString *const WindowFrameKey = @"WindowFrame";
+
+
+static void UpdateChangeCountCallback(void *userData,
+ void *object,
+ AudioUnitEvent const *inEvent,
+ UInt64 inEventHostTime,
+ AudioUnitParameterValue inParameterValue)
+{
+ [(NSDocument *)userData updateChangeCount:NSChangeDone];
+}
+
+
+@interface AUEffectDocument : NSDocument <NSWindowDelegate>
+{
+ IBOutlet NSWindow *window;
+ IBOutlet NSButton *genericViewButton;
+ IBOutlet NSPopUpButton *presetButton;
+ NSView *view;
+ NSSize headerSize;
+ CFArrayRef presets;
+ AUParameterListenerRef listener;
+ BOOL forceGenericView;
+ NSString *restoreFrame;
+
+ AudioComponentDescription description;
+ AUGraph graph;
+ AUNode outputNode, sourceNode, effectNode;
+ AudioUnit outputUnit, sourceUnit, effectUnit;
+}
+
+- (void)dealloc;
+
+- (void)makeWindowControllers;
+- (BOOL)readFromData:(NSData *)data ofType:(NSString *)type error:(NSError **)error;
+- (NSData *)dataOfType:(NSString *)type error:(NSError **)error;
+
+- (IBAction)toggleGenericView:(id)sender;
+- (IBAction)loadPreset:(id)sender;
+
+- (void)viewFrameDidChange:(NSNotification *)notification;
+
+@end
+
+@implementation AUEffectDocument
+
+- (void)loadEffectUI {
+ if ((0 == effectNode) || (nil == window))
+ return;
+
+ BOOL customViewValid = NO;
+ OSStatus status;
+ UInt32 uiDescSize;
+ AudioUnitCocoaViewInfo *viewInfo;
+ status = AudioUnitGetPropertyInfo(effectUnit,
+ kAudioUnitProperty_CocoaUI,
+ kAudioUnitScope_Global,
+ 0,
+ &uiDescSize,
+ NULL);
+ UInt32 const uiClassCount = 1 + ((uiDescSize - sizeof(*viewInfo)) / sizeof(viewInfo->mCocoaAUViewClass[0]));
+ if ((noErr == status) && (0 < uiClassCount))
+ {
+ viewInfo = (AudioUnitCocoaViewInfo *)malloc(uiDescSize);
+ status = AudioUnitGetProperty(effectUnit,
+ kAudioUnitProperty_CocoaUI,
+ kAudioUnitScope_Global,
+ 0,
+ viewInfo,
+ &uiDescSize);
+ if (noErr == status)
+ {
+ NSBundle *const bundle = [NSBundle bundleWithPath:[(NSURL *)viewInfo->mCocoaAUViewBundleLocation path]];
+ Class const viewClass = [bundle classNamed:(NSString *)viewInfo->mCocoaAUViewClass[0]];
+ if ((NULL != viewClass)
+ && [viewClass conformsToProtocol:@protocol(AUCocoaUIBase)]
+ && [viewClass instancesRespondToSelector:@selector(uiViewForAudioUnit:withSize:)])
+ {
+ customViewValid = YES;
+ if (!forceGenericView)
+ {
+ id const factory = [[viewClass alloc] init];
+ view = [factory uiViewForAudioUnit:effectUnit
+ withSize:[[window contentView] bounds].size];
+ [factory release];
+ }
+ }
+ CFRelease(viewInfo->mCocoaAUViewBundleLocation);
+ for (UInt32 i = 0; i < uiClassCount; i++)
+ CFRelease(viewInfo->mCocoaAUViewClass[i]);
+ }
+ free(viewInfo);
+ }
+ if (nil == view)
+ {
+ view = [[[AUGenericView alloc] initWithAudioUnit:effectUnit] autorelease];
+ [(AUGenericView *)view setShowsExpertParameters:YES];
+ }
+
+ [view setAutoresizingMask:NSViewNotSizable];
+ [view setFrameOrigin:NSMakePoint(0, 0)];
+ NSRect const oldFrame = [window frame];
+ NSRect const desired = [window frameRectForContentRect:[view frame]];
+ NSRect const newFrame = NSMakeRect(oldFrame.origin.x,
+ oldFrame.origin.y + oldFrame.size.height - headerSize.height - desired.size.height,
+ desired.size.width,
+ headerSize.height + desired.size.height);
+ [window setFrame:newFrame display:YES animate:NO];
+ [[window contentView] addSubview:view];
+ [view setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(viewFrameDidChange:)
+ name:NSViewFrameDidChangeNotification
+ object:view];
+
+ [genericViewButton setEnabled:customViewValid];
+ if (!customViewValid)
+ {
+ forceGenericView = YES;
+ [genericViewButton setState:NSOnState];
+ }
+
+ CFIndex const presetCount = (NULL != presets) ? CFArrayGetCount(presets) : 0;
+ [presetButton setEnabled:(0 < presetCount)];
+ while (1 < [presetButton numberOfItems])
+ [presetButton removeItemAtIndex:1];
+ for (CFIndex i = 0; i < presetCount; i++)
+ {
+ AUPreset const *preset = (AUPreset const*)CFArrayGetValueAtIndex(presets, i);
+ NSMenuItem const *item = [[presetButton menu] addItemWithTitle:(NSString *)preset->presetName
+ action:@selector(loadPreset:)
+ keyEquivalent:@""];
+ [item setTarget:self];
+ [item setTag:i];
+ }
+}
+
+- (id)init {
+ if (!(self = [super init])) return nil;
+
+ window = nil;
+ genericViewButton = nil;
+ presetButton = nil;
+ view = nil;
+ presets = NULL;
+ listener = NULL;
+ forceGenericView = NO;
+ restoreFrame = nil;
+
+ description.componentType = description.componentSubType = description.componentManufacturer = 0;
+ description.componentFlags = description.componentFlagsMask = 0;
+ graph = NULL;
+ outputNode = sourceNode = effectNode = 0;
+
+ AudioComponentDescription const outputDesc = { kAudioUnitType_Output,
+ kAudioUnitSubType_DefaultOutput,
+ kAudioUnitManufacturer_Apple,
+ 0,
+ 0, };
+ AudioComponentDescription const sourceDesc = { kAudioUnitType_Generator,
+ kAudioUnitSubType_AudioFilePlayer,
+ kAudioUnitManufacturer_Apple,
+ 0,
+ 0, };
+ if ((noErr != NewAUGraph(&graph))
+ || (noErr != AUGraphAddNode(graph, &outputDesc, &outputNode))
+ || (noErr != AUGraphAddNode(graph, &sourceDesc, &sourceNode))
+ || (noErr != AUGraphOpen(graph))
+ || (noErr != AUGraphNodeInfo(graph, outputNode, NULL, &outputUnit))
+ || (noErr != AUGraphNodeInfo(graph, sourceNode, NULL, &sourceUnit))
+ || (noErr != AUGraphInitialize(graph)))
+ {
+ [self release];
+ return nil;
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ if (NULL != presets)
+ CFRelease(presets);
+
+ if (NULL != listener)
+ AUListenerDispose(listener);
+
+ if (nil != restoreFrame)
+ [restoreFrame release];
+
+ if (NULL != graph)
+ {
+ AUGraphClose(graph);
+ DisposeAUGraph(graph);
+ }
+
+ [super dealloc];
+}
+
+- (void)makeWindowControllers {
+ genericViewButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)];
+ [genericViewButton setAutoresizingMask:NSViewNotSizable];
+ [[genericViewButton cell] setControlSize:NSSmallControlSize];
+ [genericViewButton setButtonType:NSSwitchButton];
+ [genericViewButton setBordered:NO];
+ [genericViewButton setAllowsMixedState:NO];
+ [genericViewButton setState:(forceGenericView ? NSOnState : NSOffState)];
+ [genericViewButton setTitle:@"Use generic editor view"];
+ [genericViewButton setAction:@selector(toggleGenericView:)];
+ [genericViewButton setTarget:self];
+ [genericViewButton sizeToFit];
+
+ presetButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 22) pullsDown:YES];
+ [presetButton setAutoresizingMask:NSViewNotSizable];
+ [[presetButton cell] setControlSize:NSSmallControlSize];
+ [[presetButton cell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
+ [presetButton setTitle:@"Load preset"];
+ [[[presetButton menu] addItemWithTitle:@"Load preset" action:NULL keyEquivalent:@""] setHidden:YES];
+ [presetButton sizeToFit];
+
+ CGFloat const controlWidth = MAX(NSWidth([genericViewButton frame]), NSWidth([presetButton frame]));
+ NSRect const presetFrame = NSMakeRect(17,
+ 8,
+ controlWidth,
+ NSHeight([presetButton frame]));
+ NSRect const genericViewFrame = NSMakeRect(17,
+ NSMaxY(presetFrame) + 9,
+ controlWidth,
+ NSHeight([genericViewButton frame]));
+ [genericViewButton setFrame:genericViewFrame];
+ [presetButton setFrame:presetFrame];
+
+ headerSize = NSMakeSize((2 * 17) + controlWidth, 18 + NSMaxY(genericViewFrame));
+ NSView *const container = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, headerSize.width, headerSize.height)];
+ [container setAutoresizingMask:(NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin)];
+ [container addSubview:genericViewButton];
+ [genericViewButton release];
+ [container addSubview:presetButton];
+ [presetButton release];
+
+ window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, headerSize.width, headerSize.height)
+ styleMask:(NSTitledWindowMask |
+ NSClosableWindowMask |
+ NSMiniaturizableWindowMask)
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [window setReleasedWhenClosed:NO];
+ [window setDelegate:self];
+ [window setTitle:@"Effect"];
+ [[window contentView] addSubview:container];
+ [container release];
+ [self setWindow:window];
+
+ NSWindowController *const controller = [[NSWindowController alloc] initWithWindow:window];
+ [self addWindowController:controller];
+ [controller release];
+ [window release];
+
+ [self loadEffectUI];
+ if (nil != restoreFrame)
+ {
+ [window setFrameFromString:restoreFrame];
+ }
+ else
+ {
+ NSRect const available = [[NSScreen mainScreen] visibleFrame];
+ NSRect frame = [window frame];
+ frame.origin.x = (NSWidth(available) - NSWidth(frame)) / 4;
+ frame.origin.y = (NSHeight(available) - NSHeight(frame)) * 3 / 4;
+ [window setFrame:frame display:YES animate:NO];
+ }
+}
+
+- (BOOL)readFromData:(NSData *)data ofType:(NSString *)type error:(NSError **)error {
+ OSStatus status;
+ UInt32 propertySize;
+
+ BOOL const hasWrapper = [type isEqualToString:AUEffectDocumentType];
+ if (!hasWrapper && ![type isEqualToString:AUPresetDocumentType])
+ {
+ if (NULL != error)
+ {
+ NSString *const message = [NSString stringWithFormat:@"Unsupported document type %@", type];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+
+ NSString *errDesc = nil;
+ id const desc = [NSPropertyListSerialization propertyListFromData:data
+ mutabilityOption:0
+ format:NULL
+ errorDescription:&errDesc];
+ if ((nil == desc) || ![desc isKindOfClass:[NSDictionary class]] || (nil != errDesc))
+ {
+ if (NULL != error)
+ {
+ NSString *const message = [NSString stringWithFormat:@"Error in file format (%@)", errDesc];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ if (nil != errDesc)
+ [errDesc release];
+ return NO;
+ }
+
+ id const typeValue = [desc objectForKey:(hasWrapper ? ComponentTypeKey : (NSString *)CFSTR(kAUPresetTypeKey))];
+ id const subtypeValue = [desc objectForKey:(hasWrapper ? ComponentSubTypeKey : (NSString *)CFSTR(kAUPresetSubtypeKey))];
+ id const manufacturerValue = [desc objectForKey:(hasWrapper ? ComponentManufacturerKey : (NSString *)CFSTR(kAUPresetManufacturerKey))];
+ if ((nil == typeValue) || ![typeValue isKindOfClass:[NSNumber class]]
+ || (nil == subtypeValue) || ![subtypeValue isKindOfClass:[NSNumber class]]
+ || (nil == manufacturerValue) || ![manufacturerValue isKindOfClass:[NSNumber class]]
+ || ([typeValue unsignedLongValue] != kAudioUnitType_Effect))
+ {
+ if (NULL != error)
+ {
+ NSString *const message = @"Error in effect description file format";
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+
+ if (NULL != presets)
+ {
+ CFRelease(presets);
+ presets = NULL;
+ }
+ if (NULL != listener)
+ {
+ AUListenerDispose(listener);
+ listener = NULL;
+ }
+ if (nil != view)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSViewFrameDidChangeNotification
+ object:nil];
+ [view removeFromSuperview];
+ view = nil;
+ }
+ if (0 != effectNode)
+ {
+ view = nil;
+ AUGraphRemoveNode(graph, effectNode);
+ effectNode = 0;
+ }
+
+ description.componentType = [typeValue longValue];
+ description.componentSubType = [subtypeValue longValue];
+ description.componentManufacturer = [manufacturerValue longValue];
+ status = noErr;
+ status = AUGraphClearConnections(graph);
+ if (noErr == status) status = AUGraphAddNode(graph, &description, &effectNode);
+ if (noErr == status) status = AUGraphNodeInfo(graph, effectNode, NULL, &effectUnit);
+ if (noErr == status) status = AUGraphConnectNodeInput(graph, sourceNode, 0, effectNode, 0);
+ if (noErr == status) status = AUGraphConnectNodeInput(graph, effectNode, 0, outputNode, 0);
+ if (noErr == status) status = AUGraphUpdate(graph, NULL);
+ if (noErr != status)
+ {
+ if (NULL != error)
+ {
+ NSString * const message = @"Error encountered while configuring AudioUnit graph";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+
+ CFPropertyListRef const classInfo = (CFPropertyListRef)(hasWrapper ? [desc objectForKey:ClassInfoKey] : desc);
+ if (NULL != classInfo)
+ {
+ AudioUnitParameter change = { effectUnit, kAUParameterListener_AnyParameter, 0, 0 };
+ status = AudioUnitSetProperty(effectUnit,
+ kAudioUnitProperty_ClassInfo,
+ kAudioUnitScope_Global,
+ 0,
+ &classInfo,
+ sizeof(classInfo));
+ if (noErr == status) status = AUParameterListenerNotify(NULL, NULL, &change);
+ if (noErr != status)
+ {
+ if (NULL != error)
+ {
+ NSString * const message = @"Error configuring effect";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+ }
+
+ propertySize = 0;
+ status = AudioUnitGetPropertyInfo(
+ effectUnit,
+ kAudioUnitProperty_ParameterList,
+ kAudioUnitScope_Global,
+ 0,
+ &propertySize,
+ NULL);
+ if (noErr != status)
+ {
+ if (NULL != error)
+ {
+ NSString * const message = @"Error getting effect parameters";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+ UInt32 const paramCount = propertySize / sizeof(AudioUnitParameterID);
+ if (0U < paramCount)
+ {
+ status = AUEventListenerCreate(UpdateChangeCountCallback,
+ self,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode,
+ 0.05,
+ 0.05,
+ &listener);
+ if (noErr != status)
+ {
+ if (NULL != error)
+ {
+ NSString * const message = @"Error creating AudioUnit event listener";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+ AudioUnitParameterID *const params = (AudioUnitParameterID *)malloc(propertySize);
+ AudioUnitGetProperty(
+ effectUnit,
+ kAudioUnitProperty_ParameterList,
+ kAudioUnitScope_Global,
+ 0,
+ params,
+ &propertySize);
+ if (noErr != status)
+ {
+ free(params);
+ if (NULL != error)
+ {
+ NSString * const message = @"Error getting effect parameters";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+ for (UInt32 i = 0; (i < paramCount) && (noErr == status); i++)
+ {
+ AudioUnitEvent event;
+ event.mEventType = kAudioUnitEvent_ParameterValueChange;
+ event.mArgument.mParameter.mAudioUnit = effectUnit;
+ event.mArgument.mParameter.mParameterID = params[i];
+ event.mArgument.mParameter.mScope = kAudioUnitScope_Global;
+ event.mArgument.mParameter.mElement = 0;
+ status = AUEventListenerAddEventType(listener, self, &event);
+ }
+ free(params);
+ if (noErr != status)
+ {
+ free(params);
+ if (NULL != error)
+ {
+ NSString * const message = @"Error getting effect parameters";
+ NSError *const underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return NO;
+ }
+ }
+
+ propertySize = sizeof(presets);
+ status = AudioUnitGetProperty(effectUnit,
+ kAudioUnitProperty_FactoryPresets,
+ kAudioUnitScope_Global,
+ 0,
+ &presets,
+ &propertySize);
+ if ((noErr != status) && (NULL != presets))
+ {
+ CFRelease(presets);
+ presets = NULL;
+ }
+
+ if (hasWrapper)
+ {
+ if ((nil != [desc objectForKey:ForceGenericViewKey])
+ && [[desc objectForKey:ForceGenericViewKey] respondsToSelector:@selector(boolValue)])
+ {
+ forceGenericView = [[desc objectForKey:ForceGenericViewKey] boolValue];
+ [genericViewButton setState:(forceGenericView ? NSOnState : NSOffState)];
+ }
+ if ((nil != [desc objectForKey:WindowFrameKey])
+ && [[desc objectForKey:WindowFrameKey] isKindOfClass:[NSString class]])
+ {
+ if (nil != restoreFrame) [restoreFrame release];
+ restoreFrame = [[NSString alloc] initWithString:[desc objectForKey:WindowFrameKey]];
+ }
+ }
+
+ [self loadEffectUI];
+
+ return YES;
+}
+
+- (NSData *)dataOfType:(NSString *)type error:(NSError **)error {
+ CFPropertyListRef classInfo;
+ UInt32 infoSize = sizeof(classInfo);
+ OSStatus const status = AudioUnitGetProperty(effectUnit,
+ kAudioUnitProperty_ClassInfo,
+ kAudioUnitScope_Global,
+ 0,
+ &classInfo,
+ &infoSize);
+ if (noErr != status)
+ {
+ if (NULL != error)
+ {
+ NSString const *message = @"Error getting effect settings";
+ NSError const *underlying = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ underlying, NSUnderlyingErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ return nil;
+ }
+ NSDictionary *desc = nil;
+ if ([type isEqualToString:AUEffectDocumentType])
+ {
+ NSNumber const *typeVal = [NSNumber numberWithUnsignedLong:description.componentType];
+ NSNumber const *subtypeVal = [NSNumber numberWithUnsignedLong:description.componentSubType];
+ NSNumber const *manufacturerVal = [NSNumber numberWithUnsignedLong:description.componentManufacturer];
+ NSNumber const *forceGenericViewVal = [NSNumber numberWithBool:forceGenericView];
+ NSString const *windowFrameVal = [window stringWithSavedFrame];
+ desc = [NSDictionary dictionaryWithObjectsAndKeys:typeVal, ComponentTypeKey,
+ subtypeVal, ComponentSubTypeKey,
+ manufacturerVal, ComponentManufacturerKey,
+ classInfo, ClassInfoKey,
+ forceGenericViewVal, ForceGenericViewKey,
+ windowFrameVal, WindowFrameKey,
+ nil];
+ }
+ else if ([type isEqualToString:AUPresetDocumentType])
+ {
+ desc = [NSDictionary dictionaryWithDictionary:(NSDictionary *)classInfo];
+ }
+ CFRelease(classInfo);
+ if (nil == desc)
+ {
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:@"Unsupported document type", NSLocalizedDescriptionKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ return nil;
+ }
+
+ NSString *errDesc = nil;
+ NSData *const data = [NSPropertyListSerialization dataFromPropertyList:desc
+ format:NSPropertyListXMLFormat_v1_0
+ errorDescription:&errDesc];
+ if ((nil == data) || (nil != errDesc))
+ {
+ if (NULL != error)
+ {
+ NSString *message;
+ if (nil != errDesc)
+ message = [NSString stringWithFormat:@"Error serialising effect settings: %@", errDesc];
+ else
+ message = @"Error serialising effect settings";
+ NSDictionary *const info = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey,
+ nil];
+ *error = [NSError errorWithDomain:AUEffectUtilErrorDomain code:0 userInfo:info];
+ }
+ if (nil != errDesc) [errDesc release];
+ return nil;
+ }
+ return data;
+}
+
+- (IBAction)toggleGenericView:(id)sender {
+ forceGenericView = (NSOnState == [sender state]);
+ if (nil != view)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSViewFrameDidChangeNotification
+ object:nil];
+ [view removeFromSuperview];
+ view = nil;
+ }
+ if (0 != effectNode)
+ [self loadEffectUI];
+}
+
+- (IBAction)loadPreset:(id)sender {
+ OSStatus status;
+
+ CFIndex const idx = [sender tag];
+ CFIndex const total = (NULL == presets) ? 0 : CFArrayGetCount(presets);
+ if ((0 > idx) || (total <= idx))
+ {
+ NSAlert const *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Invalid preset selected"];
+ [alert setInformativeText:[NSString stringWithFormat:@"Tried to select preset %ld of %ld",
+ (long)idx + 1,
+ (long)total]];
+ [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
+ return;
+ }
+
+ AUPreset const *preset = (AUPreset const *)CFArrayGetValueAtIndex(presets, idx);
+ status = AudioUnitSetProperty(effectUnit,
+ kAudioUnitProperty_PresentPreset,
+ kAudioUnitScope_Global,
+ 0,
+ preset,
+ sizeof(AUPreset));
+ if (noErr != status)
+ {
+ NSAlert const *alert = [[NSAlert alloc] init];
+ [alert setMessageText:[NSString stringWithFormat:@"Error loading preset %@", preset->presetName]];
+ [alert setInformativeText:[NSString stringWithFormat:@"Error %ld encountered while setting AudioUnit property",
+ (long)status]];
+ [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
+ return;
+ }
+
+ AudioUnitParameter change = { effectUnit, kAUParameterListener_AnyParameter, 0, 0 };
+ status = AUParameterListenerNotify(NULL, NULL, &change);
+ if (noErr != status)
+ {
+ NSAlert const *alert = [[NSAlert alloc] init];
+ [alert setMessageText:[NSString stringWithFormat:@"Error notifying of parameter changes for preset %@",
+ preset->presetName]];
+ [alert setInformativeText:[NSString stringWithFormat:@"Error %ld encountered while sending notification",
+ (long)status]];
+ [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
+ return;
+ }
+}
+
+- (void)viewFrameDidChange:(NSNotification *)notification {
+ NSRect const oldFrame = [window frame];
+ NSRect const desired = [window frameRectForContentRect:[[notification object] frame]];
+ NSRect const newFrame = NSMakeRect(oldFrame.origin.x,
+ oldFrame.origin.y + oldFrame.size.height - headerSize.height- desired.size.height,
+ desired.size.width,
+ headerSize.height + desired.size.height);
+ [window setFrame:newFrame display:YES animate:NO];
+}
+
+@end
+
+
+@interface AUEffectUtilAppDelegate : NSObject <NSApplicationDelegate>
+{
+ EffectInfo *effects;
+
+ IBOutlet NSMenu *newEffectMenu;
+}
+
+- (id)init;
+- (void)dealloc;
+
+- (IBAction)newEffect:(id)sender;
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification;
+- (void)applicationDidFinishLaunching:(NSNotification *)notification;
+- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender;
+
+@end
+
+@implementation AUEffectUtilAppDelegate
+
+- (void)appendApplicationMenu:(NSMenu *)parent {
+ NSMenuItem *item;
+ NSMenu *submenu;
+ NSString *const appName = [(NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()) objectForKey:@"CFBundleName"];
+
+ NSMenu *const menu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"Application"];
+ item = [parent addItemWithTitle:@"Application" action:NULL keyEquivalent:@""];
+ [parent setSubmenu:menu forItem:item];
+ [menu release];
+ [menu setValue:@"NSAppleMenu" forKey:@"name"];
+
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName] action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
+ [item setTarget:NSApp];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:@"Services" action:NULL keyEquivalent:@""];
+ submenu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"Services"];
+ [menu setSubmenu:submenu forItem:item];
+ [submenu release];
+ [NSApp setServicesMenu:submenu];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName] action:@selector(hide:) keyEquivalent:@"h"];
+ item = [menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+ [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
+ item = [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName] action:@selector(terminate:) keyEquivalent:@"q"];
+ [item setTarget:NSApp];
+}
+
+- (void)appendFileMenu:(NSMenu *)parent {
+ NSMenuItem *item;
+
+ NSMenu *const menu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"File"];
+ item = [parent addItemWithTitle:@"File" action:NULL keyEquivalent:@""];
+ [parent setSubmenu:menu forItem:item];
+ [menu release];
+
+ item = [menu addItemWithTitle:@"New" action:NULL keyEquivalent:@""];
+ newEffectMenu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"New"];
+ [menu setSubmenu:newEffectMenu forItem:item];
+ [newEffectMenu release];
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"Open%C", (unichar)0x2026] action:@selector(openDocument:) keyEquivalent:@"o"];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
+ item = [menu addItemWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@"s"];
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"Save As%C", (unichar)0x2026] action:@selector(saveDocumentAs:) keyEquivalent:@"S"];
+ item = [menu addItemWithTitle:@"Save All" action:@selector(saveAllDocuments:) keyEquivalent:@""];
+ item = [menu addItemWithTitle:@"Revert to Saved" action:@selector(revertDocumentToSaved:) keyEquivalent:@"u"];
+}
+
+- (void)appendEditMenu:(NSMenu *)parent {
+ NSMenuItem *item;
+
+ NSMenu *const menu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"Edit"];
+ item = [parent addItemWithTitle:@"Edit" action:NULL keyEquivalent:@""];
+ [parent setSubmenu:menu forItem:item];
+ [menu release];
+
+ item = [menu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
+ item = [menu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
+ item = [menu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
+ item = [menu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
+ item = [menu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
+ item = [menu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
+}
+
+- (void)appendWindowMenu:(NSMenu *)parent {
+ NSMenuItem *item;
+
+ NSMenu *const menu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"Window"];
+ item = [parent addItemWithTitle:@"Window" action:NULL keyEquivalent:@""];
+ [parent setSubmenu:menu forItem:item];
+ [menu release];
+ [NSApp setWindowsMenu:menu];
+
+ item = [menu addItemWithTitle:@"Minimize" action:@selector(performMinimize:) keyEquivalent:@"m"];
+ item = [menu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ item = [menu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
+}
+
+- (void)appendHelpMenu:(NSMenu *)parent {
+ NSMenuItem *item;
+ NSString *const appName = [(NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()) objectForKey:@"CFBundleName"];
+
+ NSMenu *const menu = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"Help"];
+ item = [parent addItemWithTitle:@"Help" action:NULL keyEquivalent:@""];
+ [parent setSubmenu:menu forItem:item];
+ [menu release];
+ [menu setValue:@"NSHelpMenu" forKey:@"name"];
+ if ([NSApp respondsToSelector:@selector(setHelpMenu:)])
+ [NSApp performSelector:@selector(setHelpMenu:) withObject:menu];
+
+ item = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ Help", appName] action:@selector(showHelp:) keyEquivalent:@"?"];
+}
+
+- (id)init {
+ if (!(self = [super init])) return nil;
+ effects = NULL;
+ return self;
+}
+
+- (void)dealloc {
+ if (effects) free(effects);
+ [super dealloc];
+}
+
+- (IBAction)newEffect:(id)sender {
+ int const index = [sender tag];
+ if ((0 > index) || (0 == effects[index].component))
+ {
+ NSAlert *const alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:@"Invalid effect component"];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ [alert release];
+ return;
+ }
+
+ NSNumber *const typeValue = [NSNumber numberWithUnsignedLong:effects[index].type];
+ NSNumber *const subtypeValue = [NSNumber numberWithUnsignedLong:effects[index].subtype];
+ NSNumber *const manufacturerValue = [NSNumber numberWithUnsignedLong:effects[index].manufacturer];
+ NSDictionary *const desc = [NSDictionary dictionaryWithObjectsAndKeys:typeValue, ComponentTypeKey,
+ subtypeValue, ComponentSubTypeKey,
+ manufacturerValue, ComponentManufacturerKey,
+ nil];
+ NSString *errDesc = nil;
+ NSData *const data = [NSPropertyListSerialization dataFromPropertyList:desc
+ format:NSPropertyListXMLFormat_v1_0
+ errorDescription:&errDesc];
+ if ((nil == data) || (nil != errDesc))
+ {
+ NSAlert *const alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:@"Error serialising properties for new effect"];
+ if (nil != errDesc) [alert setInformativeText:[errDesc autorelease]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ [alert release];
+ return;
+ }
+
+ NSError *err = nil;
+ AUEffectDocument *const document = [[AUEffectDocument alloc] init];
+ if ((nil == document) || ![document readFromData:data ofType:AUEffectDocumentType error:&err])
+ {
+ [document release];
+ if (nil != err)
+ {
+ [[NSAlert alertWithError:err] runModal];
+ }
+ else
+ {
+ NSAlert *const alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:@"Error creating new effect document"];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ [alert release];
+ }
+ return;
+ }
+
+ [document makeWindowControllers];
+ [document showWindows];
+ [[NSDocumentController sharedDocumentController] addDocument:document];
+ [document release];
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification {
+ NSMenu *const menubar = [[NSMenu allocWithZone:[NSMenu zone]] initWithTitle:@"MainMenu"];
+ [NSApp setMainMenu:menubar];
+ [menubar release];
+ [self appendApplicationMenu:menubar];
+ [self appendFileMenu:menubar];
+ [self appendEditMenu:menubar];
+ [self appendWindowMenu:menubar];
+ [self appendHelpMenu:menubar];
+
+ ProcessSerialNumber const serial = { 0, kCurrentProcess };
+ OSStatus const status = TransformProcessType(&serial, kProcessTransformToForegroundApplication);
+ if (noErr != status)
+ {
+ NSLog(@"Error transforming to foreground application (%ld)", (long)status);
+ [NSApp terminate:self];
+ }
+ else
+ {
+ [NSApp activateIgnoringOtherApps:YES];
+ }
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification {
+ ComponentDescription effectFilter = { kAudioUnitType_Effect, 0, 0, 0, 0 };
+ long const count = CountComponents(&effectFilter);
+ if (0 == count)
+ {
+ NSAlert *const alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:@"No AudioUnit effects found"];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ [alert release];
+ }
+
+ std::vector<std::pair<Component, OSStatus> > failed;
+ effects = (EffectInfo *)malloc(count * sizeof(*effects));
+ Component effect = FindNextComponent(0, &effectFilter);
+ for (long i = 0; (i < count) && (effect != 0); i++, effect = FindNextComponent(effect, &effectFilter))
+ {
+ ComponentDescription effectDesc;
+ Handle const nameHandle = NewHandle(4);
+ OSStatus const err = GetComponentInfo(effect, &effectDesc, nameHandle, NULL, NULL);
+ if (noErr == err)
+ {
+ effects[i].component = effect;
+ effects[i].type = effectDesc.componentType;
+ effects[i].subtype = effectDesc.componentSubType;
+ effects[i].manufacturer = effectDesc.componentManufacturer;
+ HLock(nameHandle);
+ CFStringRef name = CFStringCreateWithPascalString(NULL,
+ (unsigned char const *)*nameHandle,
+ kCFStringEncodingMacRoman);
+ HUnlock(nameHandle);
+ NSMenuItem *const item = [newEffectMenu addItemWithTitle:(NSString *)name
+ action:@selector(newEffect:)
+ keyEquivalent:@""];
+ [item setTag:i];
+ [item setTarget:self];
+ CFRelease(name);
+ }
+ else
+ {
+ effects[i].component = 0;
+ failed.push_back(std::make_pair(effect, err));
+ }
+ DisposeHandle(nameHandle);
+ }
+
+ if (!failed.empty())
+ {
+ NSString *const message = [NSString stringWithFormat:@"Failed to get info for %lu effect%s",
+ (unsigned long)failed.size(),
+ ((1U == failed.size()) ? "" : "s")];
+ NSMutableString *const detail = [NSMutableString stringWithCapacity:(16 * failed.size())];
+ std::vector<std::pair<Component, OSStatus> >::const_iterator it = failed.begin();
+ [detail appendFormat:@"%lu (%ld)", (unsigned long)it->first, (long)it->second];
+ ++it;
+ while (failed.end() != it)
+ {
+ [detail appendFormat:@", %lu (%ld)", (unsigned long)it->first, (long)it->second];
+ ++it;
+ }
+ NSAlert *const alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:message];
+ [alert setInformativeText:[NSString stringWithString:detail]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ [alert release];
+ }
+}
+
+- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender {
+ return NO;
+}
+
+@end
+
+
+int main(int argc, char *argv[])
+{
+ NSAutoreleasePool *pool;
+
+ // Initialise NSApplication
+ pool = [[NSAutoreleasePool alloc] init];
+ [NSApplication sharedApplication];
+ AUEffectUtilAppDelegate *const delegate = [[AUEffectUtilAppDelegate alloc] init];
+ [[NSApplication sharedApplication] setDelegate:delegate];
+ [pool release];
+
+ // Let's go!
+ pool = [[NSAutoreleasePool alloc] init];
+ [NSApp run];
+ [delegate release];
+ [pool release];
+ return 0;
+}