diff --git a/AudioUnit/AmuseContainer.entitlements.in b/AudioUnit/AmuseContainer.entitlements.in new file mode 100644 index 0000000..44210a9 --- /dev/null +++ b/AudioUnit/AmuseContainer.entitlements.in @@ -0,0 +1,14 @@ + + + + + com.apple.application-identifier + @APPLE_TEAM_ID@.@APPLE_BUNDLE_ID@ + com.apple.developer.team-identifier + @APPLE_TEAM_ID@ + com.apple.security.application-groups + + group.io.github.axiodl.Amuse.AudioGroups + + + diff --git a/AudioUnit/AmuseContainerMainMenu.xib b/AudioUnit/AmuseContainerMainMenu.xib new file mode 100644 index 0000000..66246a9 --- /dev/null +++ b/AudioUnit/AmuseContainerMainMenu.xib @@ -0,0 +1,530 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AudioUnit/AmuseContainingApp.mm b/AudioUnit/AmuseContainingApp.mm index a459a24..7b8524e 100644 --- a/AudioUnit/AmuseContainingApp.mm +++ b/AudioUnit/AmuseContainingApp.mm @@ -3,19 +3,44 @@ #import #import "AudioUnitViewController.hpp" -@interface AppDelegate : NSObject -{} +@interface MainView : NSView +{ + AudioUnitViewController* amuseVC; +} +@end + +@implementation MainView + + +- (id)initWithFrame:(NSRect)frameRect +{ + self = [super initWithFrame:frameRect]; + if (!self) + return nil; + amuseVC = [[AudioUnitViewController alloc] initWithNibName:nil bundle:nil]; + [self addSubview:amuseVC.view]; + return self; +} + +- (BOOL)translatesAutoresizingMaskIntoConstraints +{ + return NO; +} + @end @interface ViewController : NSViewController { + NSWindow* _window; NSButton* playButton; - AudioUnitViewController* amuseVC; } +@end -@property (weak) IBOutlet NSView *containerView; --(IBAction)togglePlay:(id)sender; - +@interface AppDelegate : NSObject +{ + NSWindow* mainWindow; + ViewController* containerVC; +} @end @implementation ViewController @@ -25,9 +50,9 @@ #if 0 AudioComponentDescription desc; /* Supply the correct AudioComponentDescription based on your AudioUnit type, manufacturer and creator. - + You need to supply matching settings in the AUAppExtension info.plist under: - + NSExtension NSExtensionAttributes AudioComponents @@ -35,7 +60,7 @@ type subtype manufacturer - + If you do not do this step, your AudioUnit will not work!!! */ desc.componentType = kAudioUnitType_MusicDevice; @@ -43,9 +68,9 @@ desc.componentManufacturer = 'Demo'; desc.componentFlags = 0; desc.componentFlagsMask = 0; - + [AUAudioUnit registerSubclass: AUv3InstrumentDemo.class asComponentDescription:desc name:@"Local InstrumentDemo" version: UINT32_MAX]; - + playEngine = [[SimplePlayEngine alloc] initWithComponentType: desc.componentType componentsFoundCallback: nil]; [playEngine selectAudioUnitWithComponentDescription2:desc completionHandler:^{ [self connectParametersToControls]; @@ -54,32 +79,67 @@ } -(void)loadView { - amuseVC = [[AudioUnitViewController alloc] initWithNibName:nil bundle:nil]; - self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 300)]; - [self.view addSubview:amuseVC.view]; - - self.view.translatesAutoresizingMaskIntoConstraints = NO; - - NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat: @"H:|-[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self.view)]; - [self.view addConstraints: constraints]; - - constraints = [NSLayoutConstraint constraintsWithVisualFormat: @"V:|-[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self.view)]; - [self.view addConstraints: constraints]; + self.view = [[MainView alloc] initWithFrame:[_window contentRectForFrameRect:_window.frame]]; +} + +- (id)initWithWindow:(NSWindow*)window +{ + self = [super initWithNibName:@"AmuseContainerMainMenu" bundle:nil]; + if (!self) + return nil; + _window = window; + return self; } @end @implementation AppDelegate +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"]; + + /* App menu */ +#if 0 + [NSMenu alloc] ini + NSMenu* rootMenu = [[NSMenu alloc] initWithTitle:@"main"]; + NSMenu* appMenu = [[NSMenu alloc] initWithTitle:@"Amuse"]; + NSMenuItem* quitItem = [appMenu addItemWithTitle:@"Quit Amuse" + action:@selector(quitApp:) + keyEquivalent:@"q"]; + [quitItem setKeyEquivalentModifierMask:NSCommandKeyMask]; + [[rootMenu addItemWithTitle:@"Amuse" + action:nil keyEquivalent:@""] setSubmenu:appMenu]; + [[NSApplication sharedApplication] setMainMenu:rootMenu]; + + NSRect mainScreenRect = [[NSScreen mainScreen] frame]; + mainWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(mainScreenRect.size.width / 2 - 100, + mainScreenRect.size.height / 2 - 150, 200, 300) + styleMask:NSClosableWindowMask|NSTitledWindowMask + backing:NSBackingStoreBuffered + defer:YES]; + [mainWindow setTitle:@"Amuse"]; + [[mainWindow windowController] setShouldCascadeWindows:NO]; + [mainWindow setFrameAutosaveName:@"AmuseDataWindow"]; + containerVC = [[ViewController alloc] initWithWindow:mainWindow]; + [mainWindow setContentViewController:containerVC]; + [mainWindow makeKeyAndOrderFront:nil]; +#endif +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +- (IBAction)quitApp:(id)sender +{ + [NSApp terminate:sender]; +} + @end -int main(int argc, const char * argv[]) { - NSApplication* app = [NSApplication sharedApplication]; - [app setActivationPolicy:NSApplicationActivationPolicyRegular]; - - /* Delegate (OS X callbacks) */ - AppDelegate* delegate = [AppDelegate new]; - [app setDelegate:delegate]; - [app run]; - return 0; +int main(int argc, const char * argv[]) +{ + NSApplicationMain(argc, argv); } diff --git a/AudioUnit/Amuse.entitlements.in b/AudioUnit/AmuseExtension.entitlements.in similarity index 76% rename from AudioUnit/Amuse.entitlements.in rename to AudioUnit/AmuseExtension.entitlements.in index df34d36..155e803 100644 --- a/AudioUnit/Amuse.entitlements.in +++ b/AudioUnit/AmuseExtension.entitlements.in @@ -8,5 +8,9 @@ @APPLE_TEAM_ID@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.io.github.axiodl.Amuse.AudioGroups + diff --git a/AudioUnit/AudioGroupFilePresenter.hpp b/AudioUnit/AudioGroupFilePresenter.hpp new file mode 100644 index 0000000..ccfabfb --- /dev/null +++ b/AudioUnit/AudioGroupFilePresenter.hpp @@ -0,0 +1,37 @@ +#ifndef __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__ +#define __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__ + +#import +#include +#include + +@class AudioGroupFilePresenter; + +struct AudioGroupDataCollection +{ + NSURL* m_proj = nullptr; /* Only this member set for single-file containers */ + NSURL* m_pool = nullptr; + NSURL* m_sdir = nullptr; + NSURL* m_samp = nullptr; + + bool invalidateURL(NSURL* url); + void moveURL(NSURL* oldUrl, NSURL* newUrl); + + std::unique_ptr _coordinateRead(AudioGroupFilePresenter* presenter, size_t& szOut, NSURL* url); + + std::unique_ptr coordinateProjRead(AudioGroupFilePresenter* presenter, size_t& szOut); + std::unique_ptr coordinatePoolRead(AudioGroupFilePresenter* presenter, size_t& szOut); + std::unique_ptr coordinateSdirRead(AudioGroupFilePresenter* presenter, size_t& szOut); + std::unique_ptr coordinateSampRead(AudioGroupFilePresenter* presenter, size_t& szOut); +}; + +@interface AudioGroupFilePresenter : NSObject +{ + NSURL* m_groupURL; + NSOperationQueue* m_dataQueue; + std::map m_audioGroupCollections; +} + +@end + +#endif // __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__ diff --git a/AudioUnit/AudioGroupFilePresenter.mm b/AudioUnit/AudioGroupFilePresenter.mm new file mode 100644 index 0000000..fd0e122 --- /dev/null +++ b/AudioUnit/AudioGroupFilePresenter.mm @@ -0,0 +1,191 @@ +#include "AudioGroupFilePresenter.hpp" +#include + +@implementation AudioGroupFilePresenter + +- (NSURL*)presentedItemURL +{ + return m_groupURL; +} + +- (NSOperationQueue*)presentedItemOperationQueue +{ + return m_dataQueue; +} + +bool AudioGroupDataCollection::invalidateURL(NSURL* url) +{ + bool valid = false; + if (m_proj) + { + if ([m_proj isEqual:url]) + m_proj = nullptr; + valid |= m_proj != nullptr; + } + if (m_pool) + { + if ([m_pool isEqual:url]) + m_pool = nullptr; + valid |= m_pool != nullptr; + } + if (m_sdir) + { + if ([m_sdir isEqual:url]) + m_sdir = nullptr; + valid |= m_sdir != nullptr; + } + if (m_samp) + { + if ([m_samp isEqual:url]) + m_samp = nullptr; + valid |= m_samp != nullptr; + } + return valid; +} + +void AudioGroupDataCollection::moveURL(NSURL* oldUrl, NSURL* newUrl) +{ + if (m_proj) + { + if ([m_proj isEqual:oldUrl]) + m_proj = newUrl; + } + if (m_pool) + { + if ([m_pool isEqual:oldUrl]) + m_pool = newUrl; + } + if (m_sdir) + { + if ([m_sdir isEqual:oldUrl]) + m_sdir = newUrl; + } + if (m_samp) + { + if ([m_samp isEqual:oldUrl]) + m_samp = newUrl; + } +} + +std::unique_ptr AudioGroupDataCollection::_coordinateRead(AudioGroupFilePresenter* presenter, size_t& szOut, NSURL* url) +{ + NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; + if (!coord) + return {}; + NSError* err; + __block std::unique_ptr ret; + __block size_t retSz = 0; + [coord coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err + byAccessor:^(NSURL* newUrl) + { + athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false); + if (r.hasError()) + return; + retSz = r.length(); + ret = r.readUBytes(retSz); + }]; + szOut = retSz; + return std::move(ret); +} + +std::unique_ptr AudioGroupDataCollection::coordinateProjRead(AudioGroupFilePresenter* presenter, size_t& szOut) +{ + if (!m_proj) + return {}; + return _coordinateRead(presenter, szOut, m_proj); +} + +std::unique_ptr AudioGroupDataCollection::coordinatePoolRead(AudioGroupFilePresenter* presenter, size_t& szOut) +{ + if (!m_pool) + return {}; + return _coordinateRead(presenter, szOut, m_pool); +} + +std::unique_ptr AudioGroupDataCollection::coordinateSdirRead(AudioGroupFilePresenter* presenter, size_t& szOut) +{ + if (!m_sdir) + return {}; + return _coordinateRead(presenter, szOut, m_sdir); +} + +std::unique_ptr AudioGroupDataCollection::coordinateSampRead(AudioGroupFilePresenter* presenter, size_t& szOut) +{ + if (!m_samp) + return {}; + return _coordinateRead(presenter, szOut, m_samp); +} + +- (void)accommodatePresentedSubitemDeletionAtURL:(NSURL*)url completionHandler:(void (^)(NSError* errorOrNil))completionHandler +{ + for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ;) + { + std::pair& pair = *it; + if (pair.second.invalidateURL(url)) + { + it = m_audioGroupCollections.erase(it); + continue; + } + ++it; + } + completionHandler(nil); +} + +- (void)presentedSubitemDidAppearAtURL:(NSURL*)url +{ + NSString* path = [url path]; + if (!path) + return; + + NSString* extension = [url pathExtension]; + NSString* lastComp = [url lastPathComponent]; + lastComp = [lastComp substringToIndex:[lastComp length] - [extension length]]; + AudioGroupDataCollection& collection = m_audioGroupCollections[[lastComp UTF8String]]; + + if ([extension isEqualToString:@"pro"] || [extension isEqualToString:@"proj"]) + { + collection.m_proj = url; + } + else if ([extension isEqualToString:@"poo"] || [extension isEqualToString:@"pool"]) + { + collection.m_pool = url; + } + else if ([extension isEqualToString:@"sdi"] || [extension isEqualToString:@"sdir"]) + { + collection.m_sdir = url; + } + else if ([extension isEqualToString:@"sam"] || [extension isEqualToString:@"samp"]) + { + collection.m_samp = url; + } + else + { + collection.m_proj = url; + } +} + +- (void)presentedSubitemAtURL:(NSURL*)oldUrl didMoveToURL:(NSURL*)newUrl +{ + for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it) + { + std::pair& pair = *it; + pair.second.moveURL(oldUrl, newUrl); + } +} + +- (id)init +{ + m_groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.io.github.axiodl.Amuse.AudioGroups"]; + if (!m_groupURL) + return nil; + m_dataQueue = [NSOperationQueue new]; + [NSFileCoordinator addFilePresenter:self]; + return self; +} + +- (void)dealloc +{ + [NSFileCoordinator removeFilePresenter:self]; +} + +@end diff --git a/AudioUnit/AudioUnitBackend.mm b/AudioUnit/AudioUnitBackend.mm index e59d102..0c5f746 100644 --- a/AudioUnit/AudioUnitBackend.mm +++ b/AudioUnit/AudioUnitBackend.mm @@ -19,25 +19,7 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine { std::vector> m_renderBufs; size_t m_frameBytes; - - void render(AudioBufferList* outputData) - { - if (m_renderBufs.size() < outputData->mNumberBuffers) - m_renderBufs.resize(outputData->mNumberBuffers); - - for (int i=0 ; imNumberBuffers ; ++i) - { - std::unique_ptr& buf = m_renderBufs[i]; - AudioBuffer& auBuf = outputData->mBuffers[i]; - if (!auBuf.mData) - { - buf.reset(new float[auBuf.mDataByteSize]); - auBuf.mData = buf.get(); - } - - _pumpAndMixVoices(auBuf.mDataByteSize / 2 / 4, reinterpret_cast(auBuf.mData)); - } - } + AudioBufferList* m_outputData = nullptr; boo::AudioChannelSet _getAvailableSet() { @@ -107,6 +89,21 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine void pumpAndMixVoices() { + if (m_renderBufs.size() < m_outputData->mNumberBuffers) + m_renderBufs.resize(m_outputData->mNumberBuffers); + + for (int i=0 ; imNumberBuffers ; ++i) + { + std::unique_ptr& buf = m_renderBufs[i]; + AudioBuffer& auBuf = m_outputData->mBuffers[i]; + if (!auBuf.mData) + { + buf.reset(new float[auBuf.mDataByteSize]); + auBuf.mData = buf.get(); + } + + _pumpAndMixVoices(auBuf.mDataByteSize / 2 / 4, reinterpret_cast(auBuf.mData)); + } } }; @@ -185,7 +182,8 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine - (AUInternalRenderBlock)internalRenderBlock { - AudioUnitVoiceEngine& voxEngine = static_cast(*m_booBackend); + __block AudioUnitVoiceEngine& voxEngine = static_cast(*m_booBackend); + __block amuse::Engine& amuseEngine = *m_engine; return ^AUAudioUnitStatus(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData, @@ -206,7 +204,8 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine } /* Output buffers */ - voxEngine.render(outputData); + voxEngine.m_outputData = outputData; + amuseEngine.pumpEngine(); return noErr; }; } diff --git a/AudioUnit/ContainerInfo.plist b/AudioUnit/ContainerInfo.plist index 76f5890..c8d7614 100644 --- a/AudioUnit/ContainerInfo.plist +++ b/AudioUnit/ContainerInfo.plist @@ -26,5 +26,7 @@ 10.11 NSPrincipalClass NSApplication + NSMainNibFile + AmuseContainerMainMenu diff --git a/CMakeLists.txt b/CMakeLists.txt index a413e66..64c7490 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ if(TARGET boo) if (NOT (AUDIOUNIT_LIBRARY STREQUAL AUDIOUNIT_LIBRARY-NOTFOUND)) set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}") - # Search for provision profile to make AudioUnit on OS X + # Search for provision profile to make AudioUnit extension on OS X unset(PROV_PROFILE) file(GLOB PROV_FILES "$ENV{HOME}/Library/MobileDevice/Provisioning Profiles/*.provisionprofile") foreach(FILE ${PROV_FILES}) @@ -96,14 +96,15 @@ if(TARGET boo) # Extension App add_executable(amuse-au MACOSX_BUNDLE AudioUnit/AudioUnitBackend.hpp AudioUnit/AudioUnitBackend.mm - AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm) + AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm + AudioUnit/AudioGroupFilePresenter.hpp AudioUnit/AudioGroupFilePresenter.mm) set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse.AudioUnit") - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.entitlements.in + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/AmuseExtension.entitlements.in ${CMAKE_CURRENT_BINARY_DIR}/AmuseExtension.entitlements) target_link_libraries(amuse-au amuse boo soxr ${AUDIOUNIT_LIBRARY} ${COREAUDIOKIT_LIBRARY} - ${AVFOUNDATION_LIBRARY} ${BOO_SYS_LIBS} logvisor) + ${AVFOUNDATION_LIBRARY} ${BOO_SYS_LIBS} logvisor athena-core) set_target_properties(amuse-au PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/ExtensionInfo.plist" BUNDLE_EXTENSION "appex" BUNDLE TRUE @@ -113,17 +114,27 @@ if(TARGET boo) # Containing App + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/AmuseContainerMainMenu.nib + COMMAND ibtool --errors --warnings --notices --module amuse_au_container --auto-activate-custom-fonts + --target-device mac --minimum-deployment-target 10.11 --output-format human-readable-text --compile + ${CMAKE_CURRENT_BINARY_DIR}/AmuseContainerMainMenu.nib + ${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/AmuseContainerMainMenu.xib + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/AmuseContainerMainMenu.xib + ) add_executable(amuse-au-container MACOSX_BUNDLE AudioUnit/AmuseContainingApp.mm AudioUnit/AudioUnitBackend.hpp AudioUnit/AudioUnitBackend.mm - AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm) + AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm + AudioUnit/AudioGroupFilePresenter.hpp AudioUnit/AudioGroupFilePresenter.mm + AmuseContainerMainMenu.nib) set_source_files_properties(AudioUnit/AudioUnitBackend.mm AudioUnit/AudioUnitViewController.mm - AudioUnit/AmuseContainingApp.mm + AudioUnit/AmuseContainingApp.mm AudioUnit/AudioGroupFilePresenter.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) target_link_libraries(amuse-au-container amuse boo soxr ${AUDIOUNIT_LIBRARY} ${COREAUDIOKIT_LIBRARY} - ${AVFOUNDATION_LIBRARY} ${BOO_SYS_LIBS} logvisor) + ${AVFOUNDATION_LIBRARY} ${BOO_SYS_LIBS} logvisor athena-core) set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse") - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.entitlements.in + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/AmuseContainer.entitlements.in ${CMAKE_CURRENT_BINARY_DIR}/AmuseContainer.entitlements) set_target_properties(amuse-au-container PROPERTIES @@ -145,6 +156,9 @@ if(TARGET boo) ) add_custom_command(TARGET amuse-au-container POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_BINARY_DIR}/AmuseContainerMainMenu.nib" + "$/../Resources/AmuseContainerMainMenu.nib" COMMAND codesign --force --sign ${APPLE_DEV_ID} "$/../.." VERBATIM