555 lines
18 KiB
Plaintext
555 lines
18 KiB
Plaintext
//-----------------------------------------------------------------------------
|
|
// 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 <AVFoundation/AVAudioSession.h>
|
|
#import <AudioUnit/AudioUnit.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#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);
|
|
}
|
|
}
|
|
}
|
|
}
|