diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/AudioUnit/Amuse.entitlements.in b/AudioUnit/Amuse.entitlements.in new file mode 100644 index 0000000..df34d36 --- /dev/null +++ b/AudioUnit/Amuse.entitlements.in @@ -0,0 +1,12 @@ + + + + + com.apple.application-identifier + @APPLE_TEAM_ID@.@APPLE_BUNDLE_ID@ + com.apple.developer.team-identifier + @APPLE_TEAM_ID@ + com.apple.security.app-sandbox + + + diff --git a/AudioUnit/AmuseContainingApp.mm b/AudioUnit/AmuseContainingApp.mm new file mode 100644 index 0000000..f167a9b --- /dev/null +++ b/AudioUnit/AmuseContainingApp.mm @@ -0,0 +1,85 @@ +#import +#import +#import +#import "AudioUnitViewController.hpp" + +@interface AppDelegate : NSObject +{} +@end + +@interface ViewController : NSViewController +{ + NSButton* playButton; + AudioUnitViewController* amuseVC; +} + +@property (weak) IBOutlet NSView *containerView; +-(IBAction)togglePlay:(id)sender; + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; +#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 + Item 0 + type + subtype + manufacturer + + If you do not do this step, your AudioUnit will not work!!! + */ + desc.componentType = kAudioUnitType_MusicDevice; + desc.componentSubType = 'sin3'; + 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]; + }]; +#endif +} + +-(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]; +} + +@end + +@implementation AppDelegate + +@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; +} diff --git a/AudioUnit/AudioUnitBackend.hpp b/AudioUnit/AudioUnitBackend.hpp new file mode 100644 index 0000000..ac90527 --- /dev/null +++ b/AudioUnit/AudioUnitBackend.hpp @@ -0,0 +1,56 @@ +#ifndef __AMUSE_AUDIOUNIT_BACKEND_HPP__ +#define __AMUSE_AUDIOUNIT_BACKEND_HPP__ +#ifdef __APPLE__ + +#include + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 +#define AMUSE_HAS_AUDIO_UNIT 1 + +#include + +#include "optional.hpp" + +#include "amuse/BooBackend.hpp" +#include "amuse/Engine.hpp" +#include "amuse/IBackendVoice.hpp" +#include "amuse/IBackendSubmix.hpp" +#include "amuse/IBackendVoiceAllocator.hpp" + +namespace amuse +{ + +/** Backend MIDI event reader for controlling sequencer with external hardware / software */ +class AudioUnitBackendMIDIReader : public BooBackendMIDIReader +{ + friend class AudioUnitBackendVoiceAllocator; +public: + AudioUnitBackendMIDIReader(Engine& engine) + : BooBackendMIDIReader(engine, "AudioUnit MIDI") {} +}; + +/** Backend voice allocator implementation for AudioUnit mixer */ +class AudioUnitBackendVoiceAllocator : public BooBackendVoiceAllocator +{ + friend class AudioUnitBackendMIDIReader; +public: + AudioUnitBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine) + : BooBackendVoiceAllocator(booEngine) {} +}; + +void RegisterAudioUnit(); + +} + +@interface AmuseAudioUnit : AUAudioUnit +{ + std::unique_ptr m_booBackend; + std::experimental::optional m_voxAlloc; + std::experimental::optional m_engine; + AUAudioUnitBusArray* m_outs; +} +@end + +#endif +#endif +#endif // __AMUSE_AUDIOUNIT_BACKEND_HPP__ diff --git a/AudioUnit/AudioUnitBackend.mm b/AudioUnit/AudioUnitBackend.mm new file mode 100644 index 0000000..e59d102 --- /dev/null +++ b/AudioUnit/AudioUnitBackend.mm @@ -0,0 +1,230 @@ +#include "AudioUnitBackend.hpp" +#ifdef __APPLE__ +#include +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 +#import +#import +#import + +#if !__has_feature(objc_arc) +#error ARC Required +#endif + +#include "logvisor/logvisor.hpp" +#include "audiodev/AudioVoiceEngine.hpp" + +static logvisor::Module Log("amuse::AudioUnitBackend"); + +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)); + } + } + + boo::AudioChannelSet _getAvailableSet() + { + return boo::AudioChannelSet::Stereo; + } + + std::vector> enumerateMIDIDevices() const + { + return {}; + } + + boo::ReceiveFunctor m_midiReceiver = nullptr; + + std::unique_ptr newVirtualMIDIIn(boo::ReceiveFunctor&& receiver) + { + m_midiReceiver = std::move(receiver); + return {}; + } + + std::unique_ptr newVirtualMIDIOut() + { + return {}; + } + + std::unique_ptr newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver) + { + return {}; + } + + std::unique_ptr newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver) + { + return {}; + } + + std::unique_ptr newRealMIDIOut(const char* name) + { + return {}; + } + + std::unique_ptr newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver) + { + return {}; + } + + AudioUnitVoiceEngine() + { + m_mixInfo.m_channels = _getAvailableSet(); + unsigned chCount = ChannelCount(m_mixInfo.m_channels); + + m_mixInfo.m_sampleRate = 96000.0; + m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I; + m_mixInfo.m_bitsPerSample = 32; + m_5msFrames = 96000 * 5 / 1000; + + boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap; + chMapOut.m_channelCount = 2; + chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft; + chMapOut.m_channels[1] = boo::AudioChannel::FrontRight; + + while (chMapOut.m_channelCount < chCount) + chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown; + + m_mixInfo.m_periodFrames = 2400; + + m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4; + } + + void pumpAndMixVoices() + { + } +}; + +@implementation AmuseAudioUnit +- (id)initWithComponentDescription:(AudioComponentDescription)componentDescription + options:(AudioComponentInstantiationOptions)options + error:(NSError * _Nullable *)outError; +{ + self = [super initWithComponentDescription:componentDescription options:options error:outError]; + if (!self) + return nil; + + AUAudioUnitBus* outBus = [[AUAudioUnitBus alloc] initWithFormat: + [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 + sampleRate:96000.0 + channels:2 + interleaved:TRUE] + error:outError]; + if (!outBus) + return nil; + + m_outs = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[outBus]]; + + self.maximumFramesToRender = 2400; + return self; +} + +- (BOOL)allocateRenderResourcesAndReturnError:(NSError * _Nullable *)outError +{ + if (![super allocateRenderResourcesAndReturnError:outError]) + return FALSE; + + m_booBackend = std::make_unique(); + if (!m_booBackend) + { + *outError = [NSError errorWithDomain:@"amuse" code:-1 + userInfo:@{NSLocalizedDescriptionKey:@"Unable to construct boo mixer"}]; + return FALSE; + } + + m_voxAlloc.emplace(*m_booBackend); + m_engine.emplace(*m_voxAlloc); + *outError = nil; + return TRUE; +} + +- (void)deallocateRenderResources +{ + m_engine = std::experimental::nullopt; + m_voxAlloc = std::experimental::nullopt; + m_booBackend.reset(); + [super deallocateRenderResources]; +} + +- (BOOL)renderResourcesAllocated +{ + if (m_engine) + return TRUE; + return FALSE; +} + +- (AUAudioUnitBusArray*)outputBusses +{ + return m_outs; +} + +- (BOOL)musicDeviceOrEffect +{ + return TRUE; +} + +- (NSInteger)virtualMIDICableCount +{ + return 1; +} + +- (AUInternalRenderBlock)internalRenderBlock +{ + AudioUnitVoiceEngine& voxEngine = static_cast(*m_booBackend); + + return ^AUAudioUnitStatus(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, + AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData, + const AURenderEvent* realtimeEventListHead, AURenderPullInputBlock pullInputBlock) + { + /* Process MIDI events first */ + if (voxEngine.m_midiReceiver) + { + for (const AUMIDIEvent* event = &realtimeEventListHead->MIDI ; + event != nullptr ; event = &event->next->MIDI) + { + if (event->eventType == AURenderEventMIDI) + { + voxEngine.m_midiReceiver(std::vector(std::cbegin(event->data), + std::cbegin(event->data) + event->length)); + } + } + } + + /* Output buffers */ + voxEngine.render(outputData); + return noErr; + }; +} +@end + +namespace amuse +{ + +void RegisterAudioUnit() +{ + AudioComponentDescription desc = {}; + desc.componentType = 'aumu'; + desc.componentSubType = 'amus'; + desc.componentManufacturer = 'AXDL'; + [AUAudioUnit registerSubclass:[AmuseAudioUnit class] asComponentDescription:desc name:@"Amuse" version:0100]; +} + +} + +#endif +#endif diff --git a/AudioUnit/AudioUnitViewController.hpp b/AudioUnit/AudioUnitViewController.hpp new file mode 100644 index 0000000..77da5ce --- /dev/null +++ b/AudioUnit/AudioUnitViewController.hpp @@ -0,0 +1,10 @@ +#ifndef __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__ +#define __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__ + +#import + +@interface AudioUnitViewController : AUViewController + +@end + +#endif // __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__ diff --git a/AudioUnit/AudioUnitViewController.mm b/AudioUnit/AudioUnitViewController.mm new file mode 100644 index 0000000..6cf6ef9 --- /dev/null +++ b/AudioUnit/AudioUnitViewController.mm @@ -0,0 +1,77 @@ +#import "AudioUnitViewController.hpp" +#import "AudioUnitBackend.hpp" + +#if !__has_feature(objc_arc) +#error ARC Required +#endif + +@interface AudioUnitView : NSView +{ + NSButton* m_fileButton; +} +- (void)clickFileButton; +@end + +@implementation AudioUnitView + +- (id)init +{ + self = [super initWithFrame:NSMakeRect(0, 0, 200, 300)]; + m_fileButton = [[NSButton alloc] initWithFrame:NSMakeRect(100, 100, 30, 10)]; + m_fileButton.target = self; + m_fileButton.action = @selector(clickFileButton); + [self addSubview:m_fileButton]; + return self; +} + +- (void)clickFileButton +{ + NSLog(@"Click"); +} + +@end + +@interface AudioUnitViewController () + +@end + +@implementation AudioUnitViewController { + AUAudioUnit *audioUnit; +} + +- (void) viewDidLoad { + [super viewDidLoad]; + + if (!audioUnit) { + return; + } + + // Get the parameter tree and add observers for any parameters that the UI needs to keep in sync with the AudioUnit +} + +- (void)loadView +{ + self.view = [AudioUnitView new]; +} + +- (NSSize)preferredContentSize +{ + return NSMakeSize(200, 300); +} + +- (NSSize)preferredMaximumSize +{ + return NSMakeSize(200, 300); +} + +- (NSSize)preferredMinimumSize +{ + return NSMakeSize(200, 300); +} + +- (AUAudioUnit*)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc error:(NSError**)error { + audioUnit = [[AmuseAudioUnit alloc] initWithComponentDescription:desc error:error]; + return audioUnit; +} + +@end diff --git a/AudioUnit/ContainerInfo.plist b/AudioUnit/ContainerInfo.plist new file mode 100644 index 0000000..76f5890 --- /dev/null +++ b/AudioUnit/ContainerInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + amuse-au-container + CFBundleIconFile + + CFBundleIdentifier + io.github.axiodl.Amuse + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Amuse + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.11 + NSPrincipalClass + NSApplication + + diff --git a/AudioUnit/ExtensionInfo.plist b/AudioUnit/ExtensionInfo.plist new file mode 100644 index 0000000..8cf5bcb --- /dev/null +++ b/AudioUnit/ExtensionInfo.plist @@ -0,0 +1,69 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Amuse + CFBundleExecutable + amuse-au + CFBundleIdentifier + io.github.axiodl.Amuse.AudioUnit + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Amuse + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.11 + NSExtension + + NSExtensionAttributes + + AudioComponents + + + description + Amuse + factoryFunction + AudioUnitViewController + manufacturer + AXDL + name + AXDL: Amuse + sandboxSafe + + subtype + amus + tags + + Synthesizer + + type + aumu + version + 67072 + + + NSExtensionServiceRoleType + NSExtensionServiceRoleTypeEditor + + NSExtensionPointIdentifier + com.apple.AudioUnit-UI + NSExtensionPrincipalClass + AudioUnitViewController + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 47a7a17..bfa6944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,9 @@ +project(amuse) + +if(EXISTS boo) + add_subdirectory(boo) +endif() + set(SOURCES lib/AudioGroup.cpp lib/AudioGroupData.cpp @@ -51,7 +57,8 @@ set(HEADERS unset(EXTRAS) if(TARGET boo) - include_directories(${BOO_INCLUDE_DIR} ${LOGVISOR_INCLUDE_DIR} ${ATHENA_INCLUDE_DIR}) + include_directories(${BOO_INCLUDE_DIR} ${BOO_INCLUDE_DIR}/../lib ${BOO_INCLUDE_DIR}/../soxr/src + ${LOGVISOR_INCLUDE_DIR} ${ATHENA_INCLUDE_DIR}) list(APPEND EXTRAS lib/BooBackend.cpp include/amuse/BooBackend.hpp) endif() @@ -64,6 +71,79 @@ add_library(amuse ${EXTRAS}) if(TARGET boo) + # AudioUnit Target + if (APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.10)) + set(APPLE_DEV_ID "" CACHE STRING "Mac Developer ID string 'Mac Developer: John Smith (XXXXXXXXXX)'") + set(APPLE_TEAM_ID "" CACHE STRING "Team ID string provisioned within Xcode / Apple's portal") + find_library(AVFOUNDATION_LIBRARY AVFoundation) + find_library(AUDIOUNIT_LIBRARY AudioUnit) + find_library(COREAUDIOKIT_LIBRARY CoreAudioKit) + 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 + unset(PROV_PROFILE) + file(GLOB PROV_FILES "$ENV{HOME}/Library/MobileDevice/Provisioning Profiles/*.provisionprofile") + foreach(FILE ${PROV_FILES}) + file(STRINGS "${FILE}" NAME REGEX ${APPLE_TEAM_ID}) + if(NAME) + set(PROV_PROFILE "${FILE}") + break() + endif() + endforeach() + + if(EXISTS "${PROV_PROFILE}") + # Containing App + add_executable(amuse-au-container MACOSX_BUNDLE AudioUnit/AmuseContainingApp.mm + AudioUnit/AudioUnitBackend.hpp AudioUnit/AudioUnitBackend.mm + AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm) + set_source_files_properties(AudioUnit/AudioUnitBackend.mm AudioUnit/AudioUnitViewController.mm + AudioUnit/AmuseContainingApp.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) + set_target_properties(amuse-au-container PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/ContainerInfo.plist" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.entitlements") + + set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.entitlements.in + ${CMAKE_CURRENT_BINARY_DIR}/AmuseContainer.entitlements) + + # Extension App + add_executable(amuse-au MACOSX_BUNDLE AudioUnit/AudioUnitBackend.hpp AudioUnit/AudioUnitBackend.mm + AudioUnit/AudioUnitViewController.hpp AudioUnit/AudioUnitViewController.mm) + + set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse.AudioUnit") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.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) + set_target_properties(amuse-au PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/ExtensionInfo.plist" + BUNDLE_EXTENSION "appex" BUNDLE TRUE + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/AudioUnit/Amuse.entitlements" + LINK_FLAGS "-e _NSExtensionMain -fobjc-arc -fobjc-link-runtime -fapplication-extension") + + file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/amuse-au.app/Contents/embedded.provisionprofile" INPUT "${PROV_PROFILE}") + install(CODE "file(REMOVE_RECURSE \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au-container.app/Contents/PlugIns/amuse-au.appex\")") + install(CODE "file(COPY \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au.app/\" DESTINATION + \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au-container.app/Contents/PlugIns/amuse-au.appex\" PATTERN + \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au.app/*\")") + install(CODE "message(STATUS \"Codesigning: amuse-au\")\nexecute_process(COMMAND codesign --force --sign + \"${APPLE_DEV_ID}\" --entitlements \"${CMAKE_CURRENT_BINARY_DIR}/AmuseExtension.entitlements\" + \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au-container.app/Contents/PlugIns/amuse-au.appex\")") + install(CODE "message(STATUS \"Codesigning: amuse-au-container\")\nexecute_process(COMMAND codesign --force --sign + \"${APPLE_DEV_ID}\" --entitlements \"${CMAKE_CURRENT_BINARY_DIR}/AmuseContainer.entitlements\" + \"${CMAKE_CURRENT_BINARY_DIR}/amuse-au-container.app\")") + + else() + message(WARNING "Unable to find developer provision profile; skipping Amuse-AU") + endif() + endif() + endif() + add_executable(amuseplay WIN32 driver/main.cpp) target_link_libraries(amuseplay amuse boo ${BOO_SYS_LIBS} logvisor athena-core) endif()