//----------------------------------------------------------------------------- // Project : VST SDK // // Category : Helpers // Filename : public.sdk/source/vst/interappaudio/AudioIO.mm // Created by : Steinberg, 08/2013. // Description : VST 3 InterAppAudio // Flags : clang-format SMTGSequencer // //----------------------------------------------------------------------------- // This file is part of a Steinberg SDK. It is subject to the license terms // in the LICENSE file found in the top-level directory of this distribution // and at www.steinberg.net/sdklicenses. // No part of the SDK, including this file, may be copied, modified, propagated, // or distributed except according to the terms contained in the LICENSE file. //----------------------------------------------------------------------------- #import "AudioIO.h" #import "MidiIO.h" #import "pluginterfaces/base/fstrdefs.h" #import #import #import #define FORCE_INLINE __attribute__ ((always_inline)) namespace Steinberg { namespace Vst { namespace InterAppAudio { //------------------------------------------------------------------------ static AudioBufferList* createBuffers (uint32 numChannels, uint32 maxFrames, uint32 frameSize) { AudioBufferList* result = (AudioBufferList*)malloc (sizeof (AudioBufferList) + sizeof (AudioBuffer) * numChannels); result->mNumberBuffers = numChannels; for (int32 i = 0; i < numChannels; i++) { result->mBuffers[i].mDataByteSize = maxFrames * sizeof (float); result->mBuffers[i].mData = calloc (1, result->mBuffers[i].mDataByteSize); result->mBuffers[i].mNumberChannels = 1; } return result; } //------------------------------------------------------------------------ static void freeAudioBufferList (AudioBufferList* audioBufferList) { for (uint32 i = 0; i < audioBufferList->mNumberBuffers; i++) { free (audioBufferList->mBuffers[i].mData); } free (audioBufferList); } //------------------------------------------------------------------------ NSString* AudioIO::kConnectionStateChange = @"AudioIO::kConnectionStateChange"; //------------------------------------------------------------------------ AudioIO::AudioIO () { sampleRate = [[AVAudioSession sharedInstance] sampleRate]; MidiIO::instance (); } //------------------------------------------------------------------------ AudioIO::~AudioIO () { if (ioBufferList) freeAudioBufferList (ioBufferList); } //------------------------------------------------------------------------ AudioIO* AudioIO::instance () { static AudioIO gInstance; return &gInstance; } //------------------------------------------------------------------------ tresult AudioIO::setupRemoteIO (OSType type) { if (remoteIO != nullptr) { AudioStreamBasicDescription streamFormat = {}; streamFormat.mChannelsPerFrame = 2; streamFormat.mSampleRate = sampleRate; streamFormat.mFormatID = kAudioFormatLinearPCM; streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; streamFormat.mBytesPerFrame = streamFormat.mBytesPerPacket = sizeof (Float32); streamFormat.mBitsPerChannel = 32; streamFormat.mFramesPerPacket = 1; OSStatus status = AudioUnitSetProperty (remoteIO, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &streamFormat, sizeof (streamFormat)); if (status != noErr) return kInternalError; status = AudioUnitSetProperty (remoteIO, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof (streamFormat)); if (status != noErr) return kInternalError; status = AudioUnitSetProperty (remoteIO, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 1, &maxFrames, sizeof (maxFrames)); if (status != noErr) return kInternalError; bool needInput = (type == kAudioUnitType_RemoteGenerator || type == kAudioUnitType_RemoteInstrument) == false; UInt32 flag = 1; if (needInput) { // enable IO Input status = AudioUnitSetProperty (remoteIO, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof (flag)); if (status != noErr) return kInternalError; } // enable IO Output status = AudioUnitSetProperty (remoteIO, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &flag, sizeof (flag)); if (status != noErr) return kInternalError; AURenderCallbackStruct renderCallback = {}; if (needInput) { renderCallback.inputProc = inputCallbackStatic; renderCallback.inputProcRefCon = this; status = AudioUnitSetProperty (remoteIO, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &renderCallback, sizeof (renderCallback)); if (status != noErr) return kInternalError; } renderCallback.inputProc = renderCallbackStatic; renderCallback.inputProcRefCon = this; status = AudioUnitSetProperty (remoteIO, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &renderCallback, sizeof (renderCallback)); if (status != noErr) return kInternalError; if (type == kAudioUnitType_RemoteInstrument || type == kAudioUnitType_RemoteMusicEffect) { AudioOutputUnitMIDICallbacks callBackStruct = {}; callBackStruct.userData = this; callBackStruct.MIDIEventProc = midiEventCallbackStatic; status = AudioUnitSetProperty (remoteIO, kAudioOutputUnitProperty_MIDICallbacks, kAudioUnitScope_Global, 0, &callBackStruct, sizeof (callBackStruct)); if (status != noErr) { NSLog (@"Setting MIDICallback on OutputUnit failed"); } } if (ioBufferList) freeAudioBufferList (ioBufferList); ioBufferList = createBuffers (streamFormat.mChannelsPerFrame, maxFrames, streamFormat.mBytesPerFrame); if (ioBufferList == nullptr) return kOutOfMemory; status = AudioUnitAddPropertyListener (remoteIO, kAudioUnitProperty_IsInterAppConnected, propertyChangeStatic, this); status = AudioUnitAddPropertyListener ( remoteIO, kAudioOutputUnitProperty_HostTransportState, propertyChangeStatic, this); return kResultTrue; } return kResultFalse; } //------------------------------------------------------------------------ tresult AudioIO::setupAUGraph (OSType type) { if (graph == nullptr) { OSStatus status = NewAUGraph (&graph); if (status != noErr) return kInternalError; AudioComponentDescription iOUnitDescription; iOUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple; iOUnitDescription.componentFlags = 0; iOUnitDescription.componentFlagsMask = 0; iOUnitDescription.componentType = kAudioUnitType_Output; iOUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO; AUNode remoteIONode; status = AUGraphAddNode (graph, &iOUnitDescription, &remoteIONode); if (status != noErr) return kInternalError; status = AUGraphOpen (graph); if (status != noErr) return kInternalError; status = AUGraphNodeInfo (graph, remoteIONode, nullptr, &remoteIO); if (status != noErr) return kInternalError; return setupRemoteIO (type); } return kResultFalse; } //------------------------------------------------------------------------ void AudioIO::updateInterAppAudioConnectionState () { if (remoteIO) { UInt32 connected; UInt32 dataSize = sizeof (connected); OSStatus status = AudioUnitGetProperty (remoteIO, kAudioUnitProperty_IsInterAppConnected, kAudioUnitScope_Global, 0, &connected, &dataSize); if (status == noErr) { if (interAppAudioConnected != connected) { if (connected) { UInt32 size = sizeof (HostCallbackInfo); status = AudioUnitGetProperty (remoteIO, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &hostCallback, &size); } else { memset (&hostCallback, 0, sizeof (HostCallbackInfo)); } interAppAudioConnected = connected > 0 ? true : false; [[NSNotificationCenter defaultCenter] postNotificationName:kConnectionStateChange object:nil]; } } } } //------------------------------------------------------------------------ tresult AudioIO::init (OSType type, OSType subType, OSType manufacturer, CFStringRef name) { tresult result = setupAUGraph (type); if (result != kResultTrue) return result; AudioComponentDescription desc = {type, subType, manufacturer, 0, 0}; OSStatus status = AudioOutputUnitPublish (&desc, name, 0, remoteIO); if (status != noErr) { NSLog (@"AudioOutputUnitPublish failed with status:%d", (int)status); } internalState = kInitialized; return result; } //------------------------------------------------------------------------ void AudioIO::setAudioSessionActive (bool state) { NSError* error; AVAudioSession* session = [AVAudioSession sharedInstance]; [session setPreferredSampleRate:sampleRate error:&error]; [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error]; [session setActive:(state ? YES : NO)error:&error]; } //------------------------------------------------------------------------ tresult AudioIO::start () { if (internalState == kInitialized) { bool appIsActive = [UIApplication sharedApplication].applicationState == UIApplicationStateActive; if (!(appIsActive || interAppAudioConnected)) { return kResultFalse; } setAudioSessionActive (true); Boolean graphInitialized = true; OSStatus status = AUGraphIsInitialized (graph, &graphInitialized); if (status != noErr) return kInternalError; if (graphInitialized == false) { status = AUGraphInitialize (graph); if (status != noErr) return kInternalError; updateInterAppAudioConnectionState (); } for (auto processor : audioProcessors) { processor->willStartAudio (this); } status = AUGraphStart (graph); if (status == noErr) { internalState = kStarted; updateInterAppAudioConnectionState (); return kResultTrue; } return kInternalError; } return kResultFalse; } //------------------------------------------------------------------------ tresult AudioIO::stop () { if (internalState == kStarted) { if (AUGraphStop (graph) == noErr) { for (auto processor : audioProcessors) { processor->didStopAudio (this); } internalState = kInitialized; if (interAppAudioConnected == false) setAudioSessionActive (false); return kResultTrue; } } return kResultFalse; } //------------------------------------------------------------------------ tresult AudioIO::addProcessor (IAudioIOProcessor* processor) { if (internalState == kInitialized) { audioProcessors.push_back (processor); MidiIO::instance ().addProcessor (processor); return kResultTrue; } return kResultFalse; } //------------------------------------------------------------------------ tresult AudioIO::removeProcessor (IAudioIOProcessor* processor) { if (internalState == kInitialized) { auto it = std::find (audioProcessors.begin (), audioProcessors.end (), processor); if (it != audioProcessors.end ()) { audioProcessors.erase (it); MidiIO::instance ().removeProcessor (processor); return kResultTrue; } } return kResultFalse; } //------------------------------------------------------------------------ bool AudioIO::switchToHost () { if (remoteIO && interAppAudioConnected) { CFURLRef instrumentUrl; UInt32 dataSize = sizeof (instrumentUrl); OSStatus result = AudioUnitGetProperty (remoteIO, kAudioUnitProperty_PeerURL, kAudioUnitScope_Global, 0, &instrumentUrl, &dataSize); if (result == noErr) { [[UIApplication sharedApplication] openURL:(__bridge NSURL*)instrumentUrl]; return true; } } return false; } //------------------------------------------------------------------------ bool AudioIO::sendRemoteControlEvent (AudioUnitRemoteControlEvent event) { if (remoteIO && interAppAudioConnected) { UInt32 controlEvent = event; UInt32 dataSize = sizeof (controlEvent); OSStatus status = AudioUnitSetProperty (remoteIO, kAudioOutputUnitProperty_RemoteControlToHost, kAudioUnitScope_Global, 0, &controlEvent, dataSize); return status == noErr; } return false; } //------------------------------------------------------------------------ UIImage* AudioIO::getHostIcon () { if (remoteIO && interAppAudioConnected) { return AudioOutputUnitGetHostIcon (remoteIO, 128); } return nil; } //------------------------------------------------------------------------ bool AudioIO::getBeatAndTempo (Float64& beat, Float64& tempo) { if (hostCallback.beatAndTempoProc) { if (hostCallback.beatAndTempoProc (hostCallback.hostUserData, &beat, &tempo) == noErr) return true; } tempo = staticTempo; beat = 0; return true; } //------------------------------------------------------------------------ bool AudioIO::getMusicalTimeLocation (UInt32& deltaSampleOffset, Float32& timeSigNumerator, UInt32& timeSigDenominator, Float64& downBeat) { if (hostCallback.musicalTimeLocationProc) { if (hostCallback.musicalTimeLocationProc (hostCallback.hostUserData, &deltaSampleOffset, &timeSigNumerator, &timeSigDenominator, &downBeat) == noErr) return true; } return false; } //------------------------------------------------------------------------ bool AudioIO::getTransportState (Boolean& isPlaying, Boolean& isRecording, Boolean& transportStateChanged, Float64& sampleInTimeLine, Boolean& isCycling, Float64& cycleStartBeat, Float64& cycleEndBeat) { if (hostCallback.transportStateProc2) { if (hostCallback.transportStateProc2 (hostCallback.hostUserData, &isPlaying, &isRecording, &transportStateChanged, &sampleInTimeLine, &isCycling, &cycleStartBeat, &cycleEndBeat) == noErr) return true; } return false; } //------------------------------------------------------------------------ FORCE_INLINE void AudioIO::remoteIOPropertyChanged (AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement) { if (inID == kAudioUnitProperty_IsInterAppConnected) { bool wasConnected = interAppAudioConnected; updateInterAppAudioConnectionState (); if (wasConnected != interAppAudioConnected) { stop (); start (); } } } //------------------------------------------------------------------------ FORCE_INLINE OSStatus AudioIO::renderCallback (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { if (ioData->mNumberBuffers == ioBufferList->mNumberBuffers) { for (uint32 i = 0; i < ioData->mNumberBuffers; i++) { memcpy (ioData->mBuffers[i].mData, ioBufferList->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize); } bool outputIsSilence = ioActionFlags ? *ioActionFlags & kAudioUnitRenderAction_OutputIsSilence : false; for (auto processor : audioProcessors) { outputIsSilence = false; processor->process (inTimeStamp, inBusNumber, inNumberFrames, ioData, outputIsSilence, this); } if (ioActionFlags) { *ioActionFlags = outputIsSilence ? kAudioUnitRenderAction_OutputIsSilence : 0; } } return noErr; } //------------------------------------------------------------------------ FORCE_INLINE OSStatus AudioIO::inputCallback (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { OSStatus status = AudioUnitRender (remoteIO, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioBufferList); return status; } //------------------------------------------------------------------------ FORCE_INLINE void AudioIO::midiEventCallback (UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { for (auto processor : audioProcessors) { processor->onMIDIEvent (inStatus, inData1, inData2, inOffsetSampleFrame, true); } } //------------------------------------------------------------------------ OSStatus AudioIO::inputCallbackStatic (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { AudioIO* io = (AudioIO*)inRefCon; return io->inputCallback (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); } //------------------------------------------------------------------------ OSStatus AudioIO::renderCallbackStatic (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { AudioIO* io = (AudioIO*)inRefCon; return io->renderCallback (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); } //------------------------------------------------------------------------ void AudioIO::propertyChangeStatic (void* inRefCon, AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement) { AudioIO* audioIO = (AudioIO*)inRefCon; audioIO->remoteIOPropertyChanged (inID, inScope, inElement); } //------------------------------------------------------------------------ void AudioIO::midiEventCallbackStatic (void* inRefCon, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { AudioIO* audioIO = (AudioIO*)inRefCon; audioIO->midiEventCallback (inStatus, inData1, inData2, inOffsetSampleFrame); } } } }