Initial release
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/AudioIO.h
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include "pluginterfaces/vst/vsttypes.h"
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioUnit/AUComponent.h>
|
||||
#include <vector>
|
||||
|
||||
#ifndef __OBJC__
|
||||
struct UIImage;
|
||||
struct NSString;
|
||||
#endif
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
class AudioIO;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class IMidiProcessor
|
||||
{
|
||||
public:
|
||||
virtual void onMIDIEvent (UInt32 status, UInt32 data1, UInt32 data2, UInt32 sampleOffset,
|
||||
bool withinRealtimeThread) = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class IAudioIOProcessor : public IMidiProcessor
|
||||
{
|
||||
public:
|
||||
virtual void willStartAudio (AudioIO* audioIO) = 0;
|
||||
virtual void didStopAudio (AudioIO* audioIO) = 0;
|
||||
|
||||
virtual void process (const AudioTimeStamp* timeStamp, UInt32 busNumber, UInt32 numFrames,
|
||||
AudioBufferList* ioData, bool& outputIsSilence, AudioIO* audioIO) = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class AudioIO
|
||||
{
|
||||
public:
|
||||
static AudioIO* instance ();
|
||||
|
||||
tresult init (OSType type, OSType subType, OSType manufacturer, CFStringRef name);
|
||||
|
||||
bool switchToHost ();
|
||||
bool sendRemoteControlEvent (AudioUnitRemoteControlEvent event);
|
||||
UIImage* getHostIcon ();
|
||||
|
||||
tresult start ();
|
||||
tresult stop ();
|
||||
|
||||
tresult addProcessor (IAudioIOProcessor* processor);
|
||||
tresult removeProcessor (IAudioIOProcessor* processor);
|
||||
|
||||
// accessors
|
||||
AudioUnit getRemoteIO () const { return remoteIO; }
|
||||
SampleRate getSampleRate () const { return sampleRate; }
|
||||
bool getInterAppAudioConnected () const { return interAppAudioConnected; }
|
||||
|
||||
// host context information
|
||||
bool getBeatAndTempo (Float64& beat, Float64& tempo);
|
||||
bool getMusicalTimeLocation (UInt32& deltaSampleOffset, Float32& timeSigNumerator,
|
||||
UInt32& timeSigDenominator, Float64& downBeat);
|
||||
bool getTransportState (Boolean& isPlaying, Boolean& isRecording,
|
||||
Boolean& transportStateChanged, Float64& sampleInTimeLine,
|
||||
Boolean& isCycling, Float64& cycleStartBeat, Float64& cycleEndBeat);
|
||||
|
||||
void setStaticFallbackTempo (Float64 tempo) { staticTempo = tempo; }
|
||||
Float64 getStaticFallbackTempo () const { return staticTempo; }
|
||||
|
||||
static NSString* kConnectionStateChange;
|
||||
//------------------------------------------------------------------------
|
||||
protected:
|
||||
AudioIO ();
|
||||
~AudioIO ();
|
||||
|
||||
static OSStatus inputCallbackStatic (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData);
|
||||
static OSStatus renderCallbackStatic (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData);
|
||||
static void propertyChangeStatic (void* inRefCon, AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||
AudioUnitScope inScope, AudioUnitElement inElement);
|
||||
static void midiEventCallbackStatic (void* inRefCon, UInt32 inStatus, UInt32 inData1,
|
||||
UInt32 inData2, UInt32 inOffsetSampleFrame);
|
||||
|
||||
OSStatus inputCallback (AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData);
|
||||
OSStatus renderCallback (AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData);
|
||||
void midiEventCallback (UInt32 inStatus, UInt32 inData1, UInt32 inData2,
|
||||
UInt32 inOffsetSampleFrame);
|
||||
|
||||
void remoteIOPropertyChanged (AudioUnitPropertyID inID, AudioUnitScope inScope,
|
||||
AudioUnitElement inElement);
|
||||
|
||||
void setAudioSessionActive (bool state);
|
||||
tresult setupRemoteIO (OSType type);
|
||||
tresult setupAUGraph (OSType type);
|
||||
void updateInterAppAudioConnectionState ();
|
||||
|
||||
AudioUnit remoteIO {nullptr};
|
||||
AUGraph graph {nullptr};
|
||||
AudioBufferList* ioBufferList {nullptr};
|
||||
HostCallbackInfo hostCallback {};
|
||||
UInt32 maxFrames {4096};
|
||||
Float64 staticTempo {120.};
|
||||
SampleRate sampleRate;
|
||||
bool interAppAudioConnected {false};
|
||||
std::vector<IAudioIOProcessor*> audioProcessors;
|
||||
|
||||
enum InternalState
|
||||
{
|
||||
kUninitialized,
|
||||
kInitialized,
|
||||
kStarted,
|
||||
};
|
||||
InternalState internalState {kUninitialized};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
|
||||
/// \endcond
|
||||
@@ -0,0 +1,554 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
if(SMTG_MAC)
|
||||
option(SMTG_BUILD_INTERAPPAUDIO "Enable building the iOS InterAppAudio examples (deprecated)" OFF)
|
||||
|
||||
if(XCODE AND SMTG_ENABLE_IOS_TARGETS AND SMTG_BUILD_INTERAPPAUDIO)
|
||||
|
||||
message("[SMTG] ********************************************************************************************************************************")
|
||||
message("[SMTG] * The iOS InterAppAudio wrapper is deprecated and may be removed in the next SDK update. Please switch to AudioUnit V3 on iOS. *")
|
||||
message("[SMTG] ********************************************************************************************************************************")
|
||||
|
||||
set(target interappaudio)
|
||||
|
||||
set(${target}_sources
|
||||
AudioIO.mm
|
||||
AudioIO.h
|
||||
HostApp.mm
|
||||
HostApp.h
|
||||
MidiIO.mm
|
||||
MidiIO.h
|
||||
PresetBrowserViewController.mm
|
||||
PresetBrowserViewController.h
|
||||
PresetManager.mm
|
||||
PresetManager.h
|
||||
PresetSaveViewController.mm
|
||||
PresetSaveViewController.h
|
||||
SettingsViewController.mm
|
||||
SettingsViewController.h
|
||||
VST3Editor.mm
|
||||
VST3Editor.h
|
||||
VST3Plugin.mm
|
||||
VST3Plugin.h
|
||||
VSTInterAppAudioAppDelegateBase.mm
|
||||
VSTInterAppAudioAppDelegateBase.h
|
||||
)
|
||||
add_library(${target} STATIC ${${target}_sources})
|
||||
smtg_set_platform_ios(${target})
|
||||
set_target_properties(${target}
|
||||
PROPERTIES
|
||||
${SDK_IDE_LIBS_FOLDER}
|
||||
)
|
||||
target_link_libraries(${target}
|
||||
PRIVATE
|
||||
sdk_ios
|
||||
"-framework CoreGraphics"
|
||||
"-framework UIKit"
|
||||
"-framework CoreMIDI"
|
||||
"-framework AudioToolbox"
|
||||
"-framework AVFoundation"
|
||||
)
|
||||
endif(XCODE AND SMTG_ENABLE_IOS_TARGETS AND SMTG_BUILD_INTERAPPAUDIO)
|
||||
endif(SMTG_MAC)
|
||||
@@ -0,0 +1,74 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/HostApp.h
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#import "public.sdk/source/vst/hosting/hostclasses.h"
|
||||
#import "base/source/fobject.h"
|
||||
#import "pluginterfaces/vst/ivstinterappaudio.h"
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
class VST3Plugin;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class InterAppAudioHostApp : public FObject, public HostApplication, public IInterAppAudioHost
|
||||
{
|
||||
public:
|
||||
//-----------------------------------------------------------------------------
|
||||
static InterAppAudioHostApp* instance ();
|
||||
|
||||
void setPlugin (VST3Plugin* plugin);
|
||||
VST3Plugin* getPlugin () const { return plugin; }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// IInterAppAudioHost
|
||||
tresult PLUGIN_API getScreenSize (ViewRect* size, float* scale) override;
|
||||
tresult PLUGIN_API connectedToHost () override;
|
||||
tresult PLUGIN_API switchToHost () override;
|
||||
tresult PLUGIN_API sendRemoteControlEvent (uint32 event) override;
|
||||
tresult PLUGIN_API getHostIcon (void** icon) override;
|
||||
tresult PLUGIN_API scheduleEventFromUI (Event& event) override;
|
||||
IInterAppAudioPresetManager* PLUGIN_API createPresetManager (const TUID& cid) override;
|
||||
tresult PLUGIN_API showSettingsView () override;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HostApplication
|
||||
tresult PLUGIN_API getName (String128 name) override;
|
||||
|
||||
OBJ_METHODS (InterAppAudioHostApp, FObject)
|
||||
REFCOUNT_METHODS (FObject)
|
||||
DEFINE_INTERFACES
|
||||
DEF_INTERFACE (IHostApplication)
|
||||
DEF_INTERFACE (IInterAppAudioHost)
|
||||
END_DEFINE_INTERFACES (FObject)
|
||||
protected:
|
||||
InterAppAudioHostApp ();
|
||||
|
||||
VST3Plugin* plugin {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
|
||||
/// \endcond
|
||||
@@ -0,0 +1,149 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/HostApp.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 "HostApp.h"
|
||||
|
||||
#import "AudioIO.h"
|
||||
#import "PresetManager.h"
|
||||
#import "SettingsViewController.h"
|
||||
#import "VST3Plugin.h"
|
||||
#import "base/source/updatehandler.h"
|
||||
#import "pluginterfaces/gui/iplugview.h"
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
InterAppAudioHostApp* InterAppAudioHostApp::instance ()
|
||||
{
|
||||
static InterAppAudioHostApp gInstance;
|
||||
return &gInstance;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
InterAppAudioHostApp::InterAppAudioHostApp () = default;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void InterAppAudioHostApp::setPlugin (VST3Plugin* plugin)
|
||||
{
|
||||
this->plugin = plugin;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::getName (String128 name)
|
||||
{
|
||||
String str ("InterAppAudioHost");
|
||||
str.copyTo (name, 0, 127);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::getScreenSize (ViewRect* size, float* scale)
|
||||
{
|
||||
if (size)
|
||||
{
|
||||
UIScreen* screen = [UIScreen mainScreen];
|
||||
CGSize s = [screen currentMode].size;
|
||||
UIWindow* window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
|
||||
if (window)
|
||||
{
|
||||
NSArray* subViews = [window subviews];
|
||||
if ([subViews count] == 1)
|
||||
{
|
||||
s = [[subViews objectAtIndex:0] bounds].size;
|
||||
}
|
||||
}
|
||||
size->left = 0;
|
||||
size->top = 0;
|
||||
size->right = s.width;
|
||||
size->bottom = s.height;
|
||||
if (scale)
|
||||
{
|
||||
*scale = screen.scale;
|
||||
}
|
||||
return kResultTrue;
|
||||
}
|
||||
return kInvalidArgument;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::connectedToHost ()
|
||||
{
|
||||
return AudioIO::instance ()->getInterAppAudioConnected () ? kResultTrue : kResultFalse;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::switchToHost ()
|
||||
{
|
||||
return AudioIO::instance ()->switchToHost () ? kResultTrue : kResultFalse;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::sendRemoteControlEvent (uint32 event)
|
||||
{
|
||||
return AudioIO::instance ()->sendRemoteControlEvent (
|
||||
static_cast<AudioUnitRemoteControlEvent> (event)) ?
|
||||
kResultTrue :
|
||||
kResultFalse;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::getHostIcon (void** icon)
|
||||
{
|
||||
if (icon)
|
||||
{
|
||||
UIImage* hostIcon = AudioIO::instance ()->getHostIcon ();
|
||||
if (hostIcon)
|
||||
{
|
||||
CGImageRef cgImage = [hostIcon CGImage];
|
||||
if (cgImage)
|
||||
{
|
||||
*icon = cgImage;
|
||||
return kResultTrue;
|
||||
}
|
||||
}
|
||||
return kNotImplemented;
|
||||
}
|
||||
return kInvalidArgument;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::scheduleEventFromUI (Event& event)
|
||||
{
|
||||
if (plugin)
|
||||
{
|
||||
return plugin->scheduleEventFromUI (event);
|
||||
}
|
||||
return kNotInitialized;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
IInterAppAudioPresetManager* PLUGIN_API InterAppAudioHostApp::createPresetManager (const TUID& cid)
|
||||
{
|
||||
return plugin ? new PresetManager (plugin, cid) : nullptr;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API InterAppAudioHostApp::showSettingsView ()
|
||||
{
|
||||
showIOSettings ();
|
||||
return kResultTrue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16B2555" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,73 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/MidiIO.h
|
||||
// Created by : Steinberg, 09/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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioIO.h"
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class MidiIO
|
||||
{
|
||||
public:
|
||||
static MidiIO& instance ();
|
||||
|
||||
bool setEnabled (bool state);
|
||||
bool isEnabled () const;
|
||||
|
||||
// MIDI Network is experimental, do not use yet
|
||||
void setMidiNetworkEnabled (bool state);
|
||||
bool isMidiNetworkEnabled () const;
|
||||
void setMidiNetworkPolicy (MIDINetworkConnectionPolicy policy);
|
||||
MIDINetworkConnectionPolicy getMidiNetworkPolicy () const;
|
||||
|
||||
void addProcessor (IMidiProcessor* processor);
|
||||
void removeProcessor (IMidiProcessor* processor);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
private:
|
||||
MidiIO ();
|
||||
~MidiIO ();
|
||||
|
||||
void onInput (const MIDIPacketList* pktlist);
|
||||
void onSourceAdded (MIDIObjectRef source);
|
||||
void onSetupChanged ();
|
||||
void disconnectSources ();
|
||||
|
||||
MIDIClientRef client {0};
|
||||
MIDIPortRef inputPort {0};
|
||||
MIDIEndpointRef destPort {0};
|
||||
|
||||
using MidiProcessors = std::vector<IMidiProcessor*>;
|
||||
MidiProcessors midiProcessors;
|
||||
|
||||
using ConnectionList = std::vector<MIDIEndpointRef>;
|
||||
ConnectionList connectedSources;
|
||||
|
||||
static void readProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* srcConnRefCon);
|
||||
static void notifyProc (const MIDINotification* message, void* refCon);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
@@ -0,0 +1,206 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/MidiIO.mm
|
||||
// Created by : Steinberg, 09/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 "MidiIO.h"
|
||||
|
||||
#import <CoreMIDI/MIDINetworkSession.h>
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
MidiIO& MidiIO::instance ()
|
||||
{
|
||||
static MidiIO gInstance;
|
||||
return gInstance;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
MidiIO::MidiIO () = default;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
MidiIO::~MidiIO ()
|
||||
{
|
||||
setEnabled (false);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::addProcessor (IMidiProcessor* processor)
|
||||
{
|
||||
midiProcessors.push_back (processor);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::removeProcessor (IMidiProcessor* processor)
|
||||
{
|
||||
auto it = std::find (midiProcessors.begin (), midiProcessors.end (), processor);
|
||||
if (it != midiProcessors.end ())
|
||||
{
|
||||
midiProcessors.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool MidiIO::isEnabled () const
|
||||
{
|
||||
return client != 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool MidiIO::setEnabled (bool state)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
if (client)
|
||||
return true;
|
||||
|
||||
OSStatus err;
|
||||
NSString* name = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if ((err =
|
||||
MIDIClientCreate ((__bridge CFStringRef)name, notifyProc, this, &client) != noErr))
|
||||
return false;
|
||||
|
||||
if ((err = MIDIInputPortCreate (client, CFSTR ("Input"), readProc, this, &inputPort) !=
|
||||
noErr))
|
||||
{
|
||||
MIDIClientDispose (client);
|
||||
client = 0;
|
||||
return false;
|
||||
}
|
||||
name = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleDisplayName"];
|
||||
if ((err = MIDIDestinationCreate (client, (__bridge CFStringRef)name, readProc, this,
|
||||
&destPort) != noErr))
|
||||
{
|
||||
MIDIPortDispose (inputPort);
|
||||
inputPort = 0;
|
||||
MIDIClientDispose (client);
|
||||
client = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (client == 0)
|
||||
return true;
|
||||
disconnectSources ();
|
||||
MIDIEndpointDispose (destPort);
|
||||
destPort = 0;
|
||||
MIDIPortDispose (inputPort);
|
||||
inputPort = 0;
|
||||
MIDIClientDispose (client);
|
||||
client = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::setMidiNetworkEnabled (bool state)
|
||||
{
|
||||
if (inputPort && isMidiNetworkEnabled () != state)
|
||||
{
|
||||
if (!state)
|
||||
{
|
||||
MIDIPortDisconnectSource (inputPort,
|
||||
[MIDINetworkSession defaultSession].sourceEndpoint);
|
||||
}
|
||||
[MIDINetworkSession defaultSession].enabled = state;
|
||||
if (state)
|
||||
{
|
||||
MIDIPortConnectSource (inputPort, [MIDINetworkSession defaultSession].sourceEndpoint,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool MidiIO::isMidiNetworkEnabled () const
|
||||
{
|
||||
return [MIDINetworkSession defaultSession].isEnabled;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::setMidiNetworkPolicy (MIDINetworkConnectionPolicy policy)
|
||||
{
|
||||
[MIDINetworkSession defaultSession].connectionPolicy = policy;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
MIDINetworkConnectionPolicy MidiIO::getMidiNetworkPolicy () const
|
||||
{
|
||||
return [MIDINetworkSession defaultSession].connectionPolicy;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::onInput (const MIDIPacketList* pktlist)
|
||||
{
|
||||
const MIDIPacket* packet = &pktlist->packet[0];
|
||||
for (UInt32 i = 0; i < pktlist->numPackets; i++)
|
||||
{
|
||||
for (auto processor : midiProcessors)
|
||||
{
|
||||
processor->onMIDIEvent (packet->data[0], packet->data[1], packet->data[2], 0, false);
|
||||
}
|
||||
packet = MIDIPacketNext (packet);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::onSourceAdded (MIDIObjectRef source)
|
||||
{
|
||||
connectedSources.push_back ((MIDIEndpointRef)source);
|
||||
MIDIPortConnectSource (inputPort, (MIDIEndpointRef)source, NULL);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::disconnectSources ()
|
||||
{
|
||||
for (auto source : connectedSources)
|
||||
MIDIPortDisconnectSource (inputPort, source);
|
||||
connectedSources.clear ();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::onSetupChanged ()
|
||||
{
|
||||
disconnectSources ();
|
||||
ItemCount numSources = MIDIGetNumberOfSources ();
|
||||
for (ItemCount i = 0; i < numSources; i++)
|
||||
{
|
||||
onSourceAdded (MIDIGetSource (i));
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::readProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* srcConnRefCon)
|
||||
{
|
||||
MidiIO* io = static_cast<MidiIO*> (readProcRefCon);
|
||||
io->onInput (pktlist);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void MidiIO::notifyProc (const MIDINotification* message, void* refCon)
|
||||
{
|
||||
if (message->messageID == kMIDIMsgSetupChanged)
|
||||
{
|
||||
MidiIO* mio = (MidiIO*)refCon;
|
||||
mio->onSetupChanged ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespaces
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="landscape">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PresetBrowserViewController">
|
||||
<connections>
|
||||
<outlet property="containerView" destination="klJ-ou-M84" id="oc3-yW-Wj1"/>
|
||||
<outlet property="presetTableView" destination="7ve-UC-DYv" id="DY3-Yy-Nem"/>
|
||||
<outlet property="view" destination="2" id="3"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="klJ-ou-M84">
|
||||
<rect key="frame" x="163" y="30" width="698" height="708"/>
|
||||
<subviews>
|
||||
<tableView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7ve-UC-DYv">
|
||||
<rect key="frame" x="20" y="20" width="658" height="630"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="separatorColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-1" id="FIu-tQ-zaK"/>
|
||||
<outlet property="delegate" destination="-1" id="Wmb-vA-YdQ"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IWt-40-Jgp">
|
||||
<rect key="frame" x="20" y="668" width="30" height="30"/>
|
||||
<state key="normal" title="Edit">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="toggleEditMode:" destination="-1" eventType="touchUpInside" id="uii-wI-Qvl"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="z3K-EZ-Ed5">
|
||||
<rect key="frame" x="639" y="668" width="39" height="30"/>
|
||||
<state key="normal" title="Close">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="cancel:" destination="-1" eventType="touchUpInside" id="BvF-YD-Tk0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.95000000000000007" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="7ve-UC-DYv" firstAttribute="top" secondItem="klJ-ou-M84" secondAttribute="top" constant="20" id="3Pq-2h-vKi"/>
|
||||
<constraint firstAttribute="bottom" secondItem="z3K-EZ-Ed5" secondAttribute="bottom" constant="10" id="9ML-qG-4d0"/>
|
||||
<constraint firstAttribute="trailing" secondItem="7ve-UC-DYv" secondAttribute="trailing" constant="20" id="Ioq-nG-kzq"/>
|
||||
<constraint firstItem="IWt-40-Jgp" firstAttribute="leading" secondItem="klJ-ou-M84" secondAttribute="leading" constant="20" id="ZJq-DX-wOM"/>
|
||||
<constraint firstAttribute="trailing" secondItem="z3K-EZ-Ed5" secondAttribute="trailing" constant="20" id="ag2-M7-lCo"/>
|
||||
<constraint firstAttribute="bottom" secondItem="7ve-UC-DYv" secondAttribute="bottom" constant="58" id="cj6-1q-Q3I"/>
|
||||
<constraint firstAttribute="bottom" secondItem="IWt-40-Jgp" secondAttribute="bottom" constant="10" id="kmf-ho-kIC"/>
|
||||
<constraint firstItem="7ve-UC-DYv" firstAttribute="leading" secondItem="klJ-ou-M84" secondAttribute="leading" constant="20" id="xiJ-zB-UUa"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="klJ-ou-M84" firstAttribute="leading" secondItem="2" secondAttribute="leading" constant="163" id="5LY-UL-APH"/>
|
||||
<constraint firstItem="klJ-ou-M84" firstAttribute="top" secondItem="2" secondAttribute="top" constant="30" id="G9o-k3-fOs"/>
|
||||
<constraint firstAttribute="bottom" secondItem="klJ-ou-M84" secondAttribute="bottom" constant="30" id="YXO-2R-3RQ"/>
|
||||
<constraint firstItem="klJ-ou-M84" firstAttribute="centerX" secondItem="2" secondAttribute="centerX" id="ZKb-qU-rmk"/>
|
||||
<constraint firstItem="klJ-ou-M84" firstAttribute="centerY" secondItem="2" secondAttribute="centerY" id="fR8-kU-uaI"/>
|
||||
<constraint firstAttribute="trailing" secondItem="klJ-ou-M84" secondAttribute="trailing" constant="163" id="uzi-AN-g6z"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetBrowserViewController.h
|
||||
// Created by : Steinberg, 09/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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <functional>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@interface PresetBrowserViewController
|
||||
: UIViewController <UITableViewDataSource, UITableViewDelegate>
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
- (id)initWithCallback:(std::function<void (const char* presetPath)>)callback;
|
||||
|
||||
- (void)setFactoryPresets:(NSArray*)factoryPresets userPresets:(NSArray*)userPresets;
|
||||
|
||||
@end
|
||||
|
||||
#endif // __OBJC__
|
||||
|
||||
/// \endcond
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetBrowserViewController.mm
|
||||
// Created by : Steinberg, 09/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 "PresetBrowserViewController.h"
|
||||
#import "pluginterfaces/base/funknown.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@interface PresetBrowserViewController ()
|
||||
//------------------------------------------------------------------------
|
||||
{
|
||||
IBOutlet UITableView* presetTableView;
|
||||
IBOutlet UIView* containerView;
|
||||
|
||||
std::function<void (const char* presetPath)> callback;
|
||||
Steinberg::FUID uid;
|
||||
}
|
||||
|
||||
@property (strong) NSArray* factoryPresets;
|
||||
@property (strong) NSArray* userPresets;
|
||||
@property (strong) NSArray* displayPresets;
|
||||
@property (assign) BOOL editMode;
|
||||
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@implementation PresetBrowserViewController
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (id)initWithCallback:(std::function<void (const char* presetPath)>)_callback
|
||||
{
|
||||
self = [super initWithNibName:@"PresetBrowserView" bundle:nil];
|
||||
if (self)
|
||||
{
|
||||
callback = _callback;
|
||||
|
||||
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
|
||||
UIViewController* rootViewController =
|
||||
[[UIApplication sharedApplication].windows[0] rootViewController];
|
||||
|
||||
[rootViewController presentViewController:self animated:YES completion:^{}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)setFactoryPresets:(NSArray*)factoryPresets userPresets:(NSArray*)userPresets
|
||||
{
|
||||
self.factoryPresets = factoryPresets;
|
||||
self.userPresets = userPresets;
|
||||
[self updatePresetArray];
|
||||
dispatch_async (dispatch_get_main_queue (), ^{ [presetTableView reloadData]; });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
containerView.layer.shadowOpacity = 0.5;
|
||||
containerView.layer.shadowOffset = CGSizeMake (5, 5);
|
||||
containerView.layer.shadowRadius = 5;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)updatePresetArray
|
||||
{
|
||||
if (self.userPresets)
|
||||
{
|
||||
self.displayPresets = [[self.factoryPresets arrayByAddingObjectsFromArray:self.userPresets]
|
||||
sortedArrayUsingComparator:^NSComparisonResult (NSURL* obj1, NSURL* obj2) {
|
||||
return [[obj1 lastPathComponent] caseInsensitiveCompare:[obj2 lastPathComponent]];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.displayPresets = self.factoryPresets;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)removeSelf
|
||||
{
|
||||
[self dismissViewControllerAnimated:YES completion:^{}];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
NSURL* url = [self.displayPresets objectAtIndex:indexPath.row];
|
||||
if (url)
|
||||
{
|
||||
callback ([[url path] UTF8String]);
|
||||
}
|
||||
[self removeSelf];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)toggleEditMode:(id)sender
|
||||
{
|
||||
self.editMode = !self.editMode;
|
||||
if (self.editMode)
|
||||
{
|
||||
NSMutableArray* indexPaths = [NSMutableArray new];
|
||||
for (NSURL* url in self.factoryPresets)
|
||||
{
|
||||
NSUInteger index = [self.displayPresets indexOfObjectIdenticalTo:url];
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
|
||||
}
|
||||
[presetTableView deleteRowsAtIndexPaths:indexPaths
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self updatePresetArray];
|
||||
NSMutableArray* indexPaths = [NSMutableArray new];
|
||||
for (NSURL* url in self.factoryPresets)
|
||||
{
|
||||
NSUInteger index = [self.displayPresets indexOfObjectIdenticalTo:url];
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
|
||||
}
|
||||
[presetTableView insertRowsAtIndexPaths:indexPaths
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
}
|
||||
[presetTableView setEditing:self.editMode animated:YES];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)cancel:(id)sender
|
||||
{
|
||||
if (callback)
|
||||
{
|
||||
callback (nullptr);
|
||||
}
|
||||
[self removeSelf];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (self.editMode)
|
||||
{
|
||||
return [self.userPresets count];
|
||||
}
|
||||
return [self.displayPresets count];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"PresetBrowserCell"];
|
||||
if (cell == nil)
|
||||
{
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
|
||||
reuseIdentifier:@"PresetBrowserCell"];
|
||||
}
|
||||
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
|
||||
NSURL* presetUrl = nil;
|
||||
if (self.editMode)
|
||||
{
|
||||
presetUrl = [self.userPresets objectAtIndex:indexPath.row];
|
||||
cell.detailTextLabel.text = @"User";
|
||||
}
|
||||
else
|
||||
{
|
||||
presetUrl = [self.displayPresets objectAtIndex:indexPath.row];
|
||||
if ([self.factoryPresets indexOfObject:presetUrl] == NSNotFound)
|
||||
{
|
||||
cell.detailTextLabel.text = @"User";
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.detailTextLabel.text = @"Factory";
|
||||
}
|
||||
}
|
||||
|
||||
cell.textLabel.text = [[presetUrl lastPathComponent] stringByDeletingPathExtension];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)tableView:(UITableView*)tableView canEditRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
if (self.editMode)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)tableView:(UITableView*)tableView
|
||||
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
|
||||
forRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
NSURL* presetUrl = [self.userPresets objectAtIndex:indexPath.row];
|
||||
if (presetUrl)
|
||||
{
|
||||
NSFileManager* fs = [NSFileManager defaultManager];
|
||||
NSError* error = nil;
|
||||
if ([fs removeItemAtURL:presetUrl error:&error] == NO)
|
||||
{
|
||||
auto alertController =
|
||||
[UIAlertController alertControllerWithTitle:[error localizedDescription]
|
||||
message:[error localizedRecoverySuggestion]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableArray* newArray = [NSMutableArray arrayWithArray:self.userPresets];
|
||||
[newArray removeObject:presetUrl];
|
||||
self.userPresets = newArray;
|
||||
[presetTableView deleteRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetManager.h
|
||||
// Created by : Steinberg, 10/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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VST3Plugin.h"
|
||||
#include "base/source/fstring.h"
|
||||
#include "pluginterfaces/vst/ivstinterappaudio.h"
|
||||
|
||||
#if __OBJC__
|
||||
@class NSArray, PresetBrowserViewController, PresetSaveViewController;
|
||||
#else
|
||||
struct NSArray;
|
||||
struct PresetBrowserViewController;
|
||||
struct PresetSaveViewController;
|
||||
#endif
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
class PresetManager : public FObject, public IInterAppAudioPresetManager
|
||||
{
|
||||
public:
|
||||
PresetManager (VST3Plugin* plugin, const TUID& cid);
|
||||
|
||||
tresult PLUGIN_API runLoadPresetBrowser () override;
|
||||
tresult PLUGIN_API runSavePresetBrowser () override;
|
||||
tresult PLUGIN_API loadNextPreset () override;
|
||||
tresult PLUGIN_API loadPreviousPreset () override;
|
||||
|
||||
DEFINE_INTERFACES
|
||||
DEF_INTERFACE (IInterAppAudioPresetManager)
|
||||
END_DEFINE_INTERFACES (FObject)
|
||||
REFCOUNT_METHODS (FObject)
|
||||
|
||||
private:
|
||||
enum PresetPathType
|
||||
{
|
||||
kFactory,
|
||||
kUser
|
||||
};
|
||||
NSArray* getPresetPaths (PresetPathType type);
|
||||
|
||||
tresult loadPreset (bool next);
|
||||
tresult loadPreset (const char* path);
|
||||
void savePreset (const char* path);
|
||||
|
||||
VST3Plugin* plugin;
|
||||
PresetBrowserViewController* visiblePresetBrowserViewController;
|
||||
PresetSaveViewController* visibleSavePresetViewController;
|
||||
FUID cid;
|
||||
String lastPreset;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
@@ -0,0 +1,281 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetManager.mm
|
||||
// Created by : Steinberg, 10/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 "PresetManager.h"
|
||||
#import "PresetBrowserViewController.h"
|
||||
#import "PresetSaveViewController.h"
|
||||
#import "public.sdk/source/vst/vstpresetfile.h"
|
||||
#import "pluginterfaces/vst/ivstattributes.h"
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class PresetStream : public ReadOnlyBStream, public IStreamAttributes
|
||||
{
|
||||
public:
|
||||
PresetStream (IBStream* sourceStream, TSize sourceOffset, TSize sectionSize,
|
||||
const char* utf8Path)
|
||||
: ReadOnlyBStream (sourceStream, sourceOffset, sectionSize), fileName (utf8Path)
|
||||
{
|
||||
fileName.toWideString (kCP_Utf8);
|
||||
}
|
||||
|
||||
virtual tresult PLUGIN_API getFileName (String128 name) override
|
||||
{
|
||||
if (fileName.length () > 0)
|
||||
{
|
||||
fileName.copyTo (name, 0, 128);
|
||||
return kResultTrue;
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
virtual IAttributeList* PLUGIN_API getAttributes () override { return nullptr; }
|
||||
|
||||
DEF_INTERFACES_1 (IStreamAttributes, ReadOnlyBStream)
|
||||
REFCOUNT_METHODS (ReadOnlyBStream)
|
||||
protected:
|
||||
String fileName;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
PresetManager::PresetManager (VST3Plugin* plugin, const TUID& cid)
|
||||
: plugin (plugin)
|
||||
, visiblePresetBrowserViewController (nil)
|
||||
, visibleSavePresetViewController (nil)
|
||||
, cid (cid)
|
||||
{
|
||||
id obj = [[NSUserDefaults standardUserDefaults] objectForKey:@"PresetManager|lastPreset"];
|
||||
if (obj && [obj isKindOfClass:[NSString class]])
|
||||
{
|
||||
lastPreset = [obj UTF8String];
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
NSArray* PresetManager::getPresetPaths (PresetPathType type)
|
||||
{
|
||||
if (type == kFactory)
|
||||
{
|
||||
return [[NSBundle mainBundle] URLsForResourcesWithExtension:@"vstpreset"
|
||||
subdirectory:@"Presets"];
|
||||
}
|
||||
NSFileManager* fs = [NSFileManager defaultManager];
|
||||
NSURL* documentsUrl = [fs URLForDirectory:NSDocumentDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:Nil
|
||||
create:YES
|
||||
error:NULL];
|
||||
if (documentsUrl)
|
||||
{
|
||||
NSMutableArray* userUrls = [NSMutableArray new];
|
||||
NSDirectoryEnumerator* enumerator =
|
||||
[fs enumeratorAtURL:documentsUrl
|
||||
includingPropertiesForKeys:nil
|
||||
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
|
||||
errorHandler:nil];
|
||||
for (NSURL* url in enumerator.allObjects)
|
||||
{
|
||||
if ([[url pathExtension] isEqualToString:@"vstpreset"])
|
||||
{
|
||||
[userUrls addObject:url];
|
||||
}
|
||||
}
|
||||
return [userUrls sortedArrayUsingComparator:^NSComparisonResult (NSURL* obj1, NSURL* obj2) {
|
||||
return [[obj1 lastPathComponent] caseInsensitiveCompare:[obj2 lastPathComponent]];
|
||||
}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API PresetManager::runLoadPresetBrowser ()
|
||||
{
|
||||
if (visiblePresetBrowserViewController != nil)
|
||||
return kResultFalse;
|
||||
|
||||
addRef ();
|
||||
visiblePresetBrowserViewController =
|
||||
[[PresetBrowserViewController alloc] initWithCallback:[this] (const char* path) {
|
||||
loadPreset (path);
|
||||
visiblePresetBrowserViewController = nil;
|
||||
release ();
|
||||
}];
|
||||
addRef ();
|
||||
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (visiblePresetBrowserViewController)
|
||||
{
|
||||
[visiblePresetBrowserViewController setFactoryPresets:getPresetPaths (kFactory)
|
||||
userPresets:getPresetPaths (kUser)];
|
||||
}
|
||||
release ();
|
||||
});
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API PresetManager::runSavePresetBrowser ()
|
||||
{
|
||||
if (visibleSavePresetViewController != nil)
|
||||
return kResultFalse;
|
||||
|
||||
addRef ();
|
||||
visibleSavePresetViewController =
|
||||
[[PresetSaveViewController alloc] initWithCallback:[this] (const char* path) {
|
||||
savePreset (path);
|
||||
visibleSavePresetViewController = nil;
|
||||
release ();
|
||||
}];
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API PresetManager::loadNextPreset ()
|
||||
{
|
||||
return loadPreset (true);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PLUGIN_API PresetManager::loadPreviousPreset ()
|
||||
{
|
||||
return loadPreset (false);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PresetManager::loadPreset (bool next)
|
||||
{
|
||||
NSArray* presets =
|
||||
[[getPresetPaths (kFactory) arrayByAddingObjectsFromArray:getPresetPaths (kUser)]
|
||||
sortedArrayUsingComparator:^NSComparisonResult (NSURL* obj1, NSURL* obj2) {
|
||||
return [[obj1 lastPathComponent] caseInsensitiveCompare:[obj2 lastPathComponent]];
|
||||
}];
|
||||
|
||||
__block NSUInteger index = NSNotFound;
|
||||
if (lastPreset.isEmpty () == false)
|
||||
{
|
||||
NSURL* lastUrl =
|
||||
[[NSURL fileURLWithPath:[NSString stringWithUTF8String:lastPreset]] fileReferenceURL];
|
||||
if (lastUrl)
|
||||
{
|
||||
[presets enumerateObjectsUsingBlock:^(NSURL* obj, NSUInteger idx, BOOL* stop) {
|
||||
if ([[obj fileReferenceURL] isEqual:lastUrl])
|
||||
{
|
||||
index = idx;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
if (index == NSNotFound)
|
||||
{
|
||||
if (next)
|
||||
index = [presets count] - 1;
|
||||
else
|
||||
index = 1;
|
||||
}
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
if (next)
|
||||
{
|
||||
if (index >= [presets count] - 1)
|
||||
index = 0;
|
||||
else
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index == 0)
|
||||
index = [presets count] - 1;
|
||||
else
|
||||
index--;
|
||||
}
|
||||
return loadPreset ([[[presets objectAtIndex:index] path] UTF8String]);
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
tresult PresetManager::loadPreset (const char* path)
|
||||
{
|
||||
if (path)
|
||||
{
|
||||
IPtr<IBStream> stream = owned (FileStream::open (path, "r"));
|
||||
if (stream)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithUTF8String:path]
|
||||
forKey:@"PresetManager|lastPreset"];
|
||||
lastPreset = path;
|
||||
auto component = U::cast<IComponent> (plugin->getAudioProcessor ());
|
||||
IEditController* controller = plugin->getEditController ();
|
||||
if (component)
|
||||
{
|
||||
PresetFile pf (stream);
|
||||
if (!pf.readChunkList ())
|
||||
return kResultFalse;
|
||||
if (pf.getClassID () != cid)
|
||||
return kResultFalse;
|
||||
const PresetFile::Entry* e = pf.getEntry (kComponentState);
|
||||
if (e == nullptr)
|
||||
return kResultFalse;
|
||||
auto filename = strrchr (path, '/');
|
||||
if (filename)
|
||||
filename++;
|
||||
IPtr<PresetStream> readOnlyBStream =
|
||||
owned (new PresetStream (stream, e->offset, e->size, filename));
|
||||
tresult result = component->setState (readOnlyBStream);
|
||||
if ((result == kResultTrue || result == kNotImplemented) && controller)
|
||||
{
|
||||
readOnlyBStream->seek (0, IBStream::kIBSeekSet);
|
||||
controller->setComponentState (readOnlyBStream);
|
||||
if (pf.contains (kControllerState))
|
||||
{
|
||||
e = pf.getEntry (kControllerState);
|
||||
if (e)
|
||||
{
|
||||
readOnlyBStream =
|
||||
owned (new PresetStream (stream, e->offset, e->size, filename));
|
||||
controller->setState (readOnlyBStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void PresetManager::savePreset (const char* path)
|
||||
{
|
||||
IBStream* stream = FileStream::open (path, "w");
|
||||
if (stream)
|
||||
{
|
||||
auto component = U::cast<IComponent> (plugin->getAudioProcessor ());
|
||||
IEditController* controller = plugin->getEditController ();
|
||||
if (component)
|
||||
{
|
||||
PresetFile::savePreset (stream, cid, component, controller);
|
||||
}
|
||||
stream->release ();
|
||||
loadPreset (path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespaces
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="ipad10_5" orientation="landscape">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PresetSaveViewController">
|
||||
<connections>
|
||||
<outlet property="containerView" destination="SIT-sP-q5k" id="Pam-4d-cjt"/>
|
||||
<outlet property="presetName" destination="4mQ-Y5-WZh" id="MJT-bn-9QR"/>
|
||||
<outlet property="view" destination="2" id="0Me-yB-Sts"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SIT-sP-q5k">
|
||||
<rect key="frame" x="256" y="192" width="512" height="104"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Preset Name :" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EGr-Dl-Wf3">
|
||||
<rect key="frame" x="20" y="20" width="108" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="4mQ-Y5-WZh">
|
||||
<rect key="frame" x="147" y="16" width="345" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-1" id="LIF-nc-eQa"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v51-mb-0Tu">
|
||||
<rect key="frame" x="455" y="68" width="37" height="33"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<state key="normal" title="Save"/>
|
||||
<connections>
|
||||
<action selector="save:" destination="-1" eventType="touchUpInside" id="J1n-Ix-DQA"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SBt-7v-tI9">
|
||||
<rect key="frame" x="20" y="68" width="53" height="33"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<state key="normal" title="Cancel">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="cancel:" destination="-1" eventType="touchUpInside" id="ibs-yW-oi5"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.95000000000000007" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="4mQ-Y5-WZh" secondAttribute="trailing" constant="20" id="02g-de-fEz"/>
|
||||
<constraint firstItem="SBt-7v-tI9" firstAttribute="leading" secondItem="SIT-sP-q5k" secondAttribute="leading" constant="20" id="9Qx-G1-uxi"/>
|
||||
<constraint firstAttribute="bottom" secondItem="v51-mb-0Tu" secondAttribute="bottom" constant="3" id="FL7-Pl-p0S"/>
|
||||
<constraint firstItem="4mQ-Y5-WZh" firstAttribute="top" secondItem="SIT-sP-q5k" secondAttribute="top" constant="16" id="M1k-Wm-yVW"/>
|
||||
<constraint firstItem="4mQ-Y5-WZh" firstAttribute="leading" secondItem="EGr-Dl-Wf3" secondAttribute="trailing" constant="19" id="Mrh-oC-pT4"/>
|
||||
<constraint firstItem="EGr-Dl-Wf3" firstAttribute="leading" secondItem="SIT-sP-q5k" secondAttribute="leading" constant="20" id="dMD-jj-Yya"/>
|
||||
<constraint firstItem="EGr-Dl-Wf3" firstAttribute="top" secondItem="SIT-sP-q5k" secondAttribute="top" constant="20" id="dbO-Rh-IOx"/>
|
||||
<constraint firstAttribute="bottom" secondItem="SBt-7v-tI9" secondAttribute="bottom" constant="3" id="lZC-gE-21j"/>
|
||||
<constraint firstAttribute="height" constant="104" id="wzX-0f-wG5"/>
|
||||
<constraint firstAttribute="trailing" secondItem="v51-mb-0Tu" secondAttribute="trailing" constant="20" id="x9j-uT-byM"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="SIT-sP-q5k" secondAttribute="trailing" constant="256" id="QRq-bk-Kbd"/>
|
||||
<constraint firstItem="SIT-sP-q5k" firstAttribute="top" secondItem="2" secondAttribute="top" constant="192" id="uF1-Qs-yD6"/>
|
||||
<constraint firstItem="SIT-sP-q5k" firstAttribute="leading" secondItem="2" secondAttribute="leading" constant="256" id="yDr-cE-KWA"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetSaveViewController.h
|
||||
// Created by : Steinberg, 09/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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <functional>
|
||||
|
||||
@interface PresetSaveViewController : UIViewController <UIAlertViewDelegate, UITextFieldDelegate>
|
||||
|
||||
- (id)initWithCallback:(std::function<void (const char* presetPath)>)callback;
|
||||
|
||||
@end
|
||||
|
||||
#endif //__OBJC__
|
||||
|
||||
/// \endcond
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/PresetSaveViewController.mm
|
||||
// Created by : Steinberg, 09/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 "PresetSaveViewController.h"
|
||||
#import "pluginterfaces/base/funknown.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@interface PresetSaveViewController ()
|
||||
//------------------------------------------------------------------------
|
||||
{
|
||||
IBOutlet UIView* containerView;
|
||||
IBOutlet UITextField* presetName;
|
||||
|
||||
std::function<void (const char* presetPath)> callback;
|
||||
Steinberg::FUID uid;
|
||||
}
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@implementation PresetSaveViewController
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (id)initWithCallback:(std::function<void (const char* presetPath)>)_callback
|
||||
{
|
||||
self = [super initWithNibName:@"PresetSaveView" bundle:nil];
|
||||
if (self)
|
||||
{
|
||||
callback = _callback;
|
||||
|
||||
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
|
||||
UIViewController* rootViewController =
|
||||
[[UIApplication sharedApplication].windows[0] rootViewController];
|
||||
|
||||
[rootViewController presentViewController:self
|
||||
animated:YES
|
||||
completion:^{ [self showKeyboard]; }];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
containerView.layer.shadowOpacity = 0.5;
|
||||
containerView.layer.shadowOffset = CGSizeMake (5, 5);
|
||||
containerView.layer.shadowRadius = 5;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)showKeyboard
|
||||
{
|
||||
[presetName becomeFirstResponder];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)removeSelf
|
||||
{
|
||||
[self dismissViewControllerAnimated:YES completion:^{}];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (NSURL*)presetURL
|
||||
{
|
||||
NSFileManager* fs = [NSFileManager defaultManager];
|
||||
NSURL* documentsUrl = [fs URLForDirectory:NSDocumentDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:Nil
|
||||
create:YES
|
||||
error:NULL];
|
||||
if (documentsUrl)
|
||||
{
|
||||
NSURL* presetPath = [[documentsUrl URLByAppendingPathComponent:presetName.text]
|
||||
URLByAppendingPathExtension:@"vstpreset"];
|
||||
return presetPath;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*)textField
|
||||
{
|
||||
if ([textField.text length] > 0)
|
||||
{
|
||||
[self save:textField];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)save:(id)sender
|
||||
{
|
||||
if (callback)
|
||||
{
|
||||
NSURL* presetPath = [self presetURL];
|
||||
NSFileManager* fs = [NSFileManager defaultManager];
|
||||
if ([fs fileExistsAtPath:[presetPath path]])
|
||||
{
|
||||
// alert for overwrite
|
||||
auto alertController = [UIAlertController
|
||||
alertControllerWithTitle:NSLocalizedString (
|
||||
@"A Preset with this name already exists",
|
||||
"Alert title")
|
||||
message:NSLocalizedString (@"Save it anyway ?", "Alert message")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController
|
||||
addAction:[UIAlertAction
|
||||
actionWithTitle:NSLocalizedString (@"Save", "Alert Save Button")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction* _Nonnull action) {
|
||||
callback ([[[self presetURL] path] UTF8String]);
|
||||
[self removeSelf];
|
||||
}]];
|
||||
[alertController
|
||||
addAction:[UIAlertAction
|
||||
actionWithTitle:NSLocalizedString (@"Cancel", "Alert Cancel Button")
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction* _Nonnull action) {}]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
callback ([[presetPath path] UTF8String]);
|
||||
}
|
||||
[self removeSelf];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)cancel:(id)sender
|
||||
{
|
||||
if (callback)
|
||||
{
|
||||
callback (nullptr);
|
||||
}
|
||||
[self removeSelf];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SettingsViewController">
|
||||
<connections>
|
||||
<outlet property="containerView" destination="RNQ-ag-wXs" id="Snq-jH-mGX"/>
|
||||
<outlet property="midiOnSwitch" destination="RUK-3J-c68" id="fsn-qn-B1b"/>
|
||||
<outlet property="tempoView" destination="7Wa-1f-ZEh" id="aAo-kk-UXa"/>
|
||||
<outlet property="view" destination="2" id="3"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RNQ-ag-wXs">
|
||||
<rect key="frame" x="272" y="266" width="481" height="236"/>
|
||||
<subviews>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RUK-3J-c68">
|
||||
<rect key="frame" x="165" y="92" width="51" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="enableMidi:" destination="-1" eventType="valueChanged" id="W1y-Cl-XZT"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enable MIDI Input" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uh8-Ad-fHW">
|
||||
<rect key="frame" x="20" y="97" width="137" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gsH-Se-9I1">
|
||||
<rect key="frame" x="220" y="186" width="40" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Close">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="close:" destination="-1" eventType="touchUpInside" id="8fR-1E-VU7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<pickerView contentMode="scaleAspectFit" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7Wa-1f-ZEh">
|
||||
<rect key="frame" x="329" y="0.0" width="86" height="216"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-1" id="kTa-mw-jgX"/>
|
||||
<outlet property="delegate" destination="-1" id="afl-U5-l43"/>
|
||||
</connections>
|
||||
</pickerView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="BPM" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ccr-8n-eGh">
|
||||
<rect key="frame" x="423" y="97" width="38" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Tempo :" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jiY-aT-q6h">
|
||||
<rect key="frame" x="222" y="97" width="97" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.95000000000000007" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="481" id="CNb-19-uMh"/>
|
||||
<constraint firstAttribute="height" constant="236" id="Gag-Ic-lk4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="RNQ-ag-wXs" firstAttribute="centerX" secondItem="2" secondAttribute="centerX" id="d7Y-kL-wHV"/>
|
||||
<constraint firstItem="RNQ-ag-wXs" firstAttribute="centerY" secondItem="2" secondAttribute="centerY" id="rA4-Fa-esq"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="297" y="-102"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/SettingsViewController.h
|
||||
// Created by : Steinberg, 09/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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SettingsViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
#endif // __OBJC__
|
||||
|
||||
extern void showIOSettings ();
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/SettingsViewController.mm
|
||||
// Created by : Steinberg, 09/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 "SettingsViewController.h"
|
||||
|
||||
#import "AudioIO.h"
|
||||
#import "MidiIO.h"
|
||||
#import <CoreMIDI/MIDINetworkSession.h>
|
||||
|
||||
using namespace Steinberg::Vst::InterAppAudio;
|
||||
|
||||
static const NSUInteger kMinTempo = 30;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@interface SettingsViewController ()
|
||||
//------------------------------------------------------------------------
|
||||
{
|
||||
IBOutlet UIView* containerView;
|
||||
IBOutlet UISwitch* midiOnSwitch;
|
||||
IBOutlet UIPickerView* tempoView;
|
||||
}
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@implementation SettingsViewController
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (id)init
|
||||
{
|
||||
self = [super initWithNibName:@"SettingsView" bundle:nil];
|
||||
if (self)
|
||||
{
|
||||
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
containerView.layer.shadowOpacity = 0.5;
|
||||
containerView.layer.shadowOffset = CGSizeMake (5, 5);
|
||||
containerView.layer.shadowRadius = 5;
|
||||
|
||||
midiOnSwitch.on = MidiIO::instance ().isEnabled ();
|
||||
|
||||
Float64 tempo = AudioIO::instance ()->getStaticFallbackTempo ();
|
||||
[tempoView selectRow:tempo - kMinTempo inComponent:0 animated:YES];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)enableMidi:(id)sender
|
||||
{
|
||||
BOOL state = midiOnSwitch.on;
|
||||
MidiIO::instance ().setEnabled (state);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (IBAction)close:(id)sender
|
||||
{
|
||||
[self dismissViewControllerAnimated:YES completion:^{}];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)pickerView:(UIPickerView*)pickerView
|
||||
didSelectRow:(NSInteger)row
|
||||
inComponent:(NSInteger)component
|
||||
{
|
||||
AudioIO::instance ()->setStaticFallbackTempo (row + kMinTempo);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (NSInteger)pickerView:(UIPickerView*)pickerView numberOfRowsInComponent:(NSInteger)component
|
||||
{
|
||||
return 301 - kMinTempo;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (NSString*)pickerView:(UIPickerView*)pickerView
|
||||
titleForRow:(NSInteger)row
|
||||
forComponent:(NSInteger)component
|
||||
{
|
||||
return [@(row + kMinTempo) stringValue];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void showIOSettings ()
|
||||
{
|
||||
SettingsViewController* controller = [[SettingsViewController alloc] init];
|
||||
|
||||
UIViewController* rootViewController =
|
||||
[[UIApplication sharedApplication].windows[0] rootViewController];
|
||||
[rootViewController presentViewController:controller animated:YES completion:^{}];
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VST3Editor.h
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#import "base/source/fobject.h"
|
||||
#import "pluginterfaces/gui/iplugview.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
class IEditController;
|
||||
|
||||
namespace InterAppAudio {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class VST3Editor : public FObject, public IPlugFrame
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------
|
||||
VST3Editor ();
|
||||
virtual ~VST3Editor ();
|
||||
|
||||
bool init (const CGRect& frame);
|
||||
bool attach (IEditController* editController);
|
||||
|
||||
UIViewController* getViewController () const { return viewController; }
|
||||
|
||||
OBJ_METHODS (VST3Editor, FObject)
|
||||
REFCOUNT_METHODS (FObject)
|
||||
DEFINE_INTERFACES
|
||||
DEF_INTERFACE (IPlugFrame)
|
||||
END_DEFINE_INTERFACES (FObject)
|
||||
protected:
|
||||
// IPlugFrame
|
||||
tresult PLUGIN_API resizeView (IPlugView* view, ViewRect* newSize) override;
|
||||
|
||||
IPlugView* plugView {nullptr};
|
||||
UIViewController* viewController {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
|
||||
/// \endcond
|
||||
@@ -0,0 +1,112 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VST3Editor.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 "VST3Editor.h"
|
||||
#import "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@interface VST3EditorViewController : UIViewController
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@implementation VST3EditorViewController
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3Editor::VST3Editor () = default;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3Editor::~VST3Editor ()
|
||||
{
|
||||
if (plugView)
|
||||
{
|
||||
plugView->release ();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool VST3Editor::init (const CGRect& frame)
|
||||
{
|
||||
viewController = [VST3EditorViewController new];
|
||||
viewController.view = [[UIView alloc] initWithFrame:frame];
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool VST3Editor::attach (IEditController* editController)
|
||||
{
|
||||
auto ec2 = U::cast<IEditController2> (editController);
|
||||
if (ec2)
|
||||
{
|
||||
ec2->setKnobMode (kLinearMode);
|
||||
}
|
||||
plugView = editController->createView (ViewType::kEditor);
|
||||
if (plugView)
|
||||
{
|
||||
if (plugView->isPlatformTypeSupported (kPlatformTypeUIView) == kResultTrue)
|
||||
{
|
||||
plugView->setFrame (this);
|
||||
if (plugView->attached ((__bridge void*)viewController.view, kPlatformTypeUIView) ==
|
||||
kResultTrue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
plugView->release ();
|
||||
plugView = nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3Editor::resizeView (IPlugView* view, ViewRect* newSize)
|
||||
{
|
||||
if (newSize && plugView && plugView == view)
|
||||
{
|
||||
if (view->onSize (newSize) == kResultTrue)
|
||||
return kResultTrue;
|
||||
return kResultFalse;
|
||||
}
|
||||
return kInvalidArgument;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // InterAppAudio
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,133 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VST3Plugin.h
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \cond ignore
|
||||
|
||||
#import "AudioIO.h"
|
||||
#import "public.sdk/source/vst/hosting/eventlist.h"
|
||||
#import "public.sdk/source/vst/hosting/parameterchanges.h"
|
||||
#import "public.sdk/source/vst/hosting/processdata.h"
|
||||
#import "public.sdk/source/vst/utility/ringbuffer.h"
|
||||
#import "base/source/fobject.h"
|
||||
#import "base/source/timer.h"
|
||||
#import "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||
#import "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
#import "pluginterfaces/vst/ivstprocesscontext.h"
|
||||
#import <atomic>
|
||||
#import <map>
|
||||
|
||||
#ifndef __OBJC__
|
||||
struct NSData;
|
||||
#endif
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
static const int32 kMaxUIEvents = 100;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class VST3Plugin : public FObject,
|
||||
public IComponentHandler,
|
||||
public IAudioIOProcessor,
|
||||
public ITimerCallback
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------
|
||||
VST3Plugin ();
|
||||
virtual ~VST3Plugin ();
|
||||
|
||||
bool init ();
|
||||
|
||||
IEditController* getEditController () const { return editController; }
|
||||
IAudioProcessor* getAudioProcessor () const { return processor; }
|
||||
|
||||
tresult scheduleEventFromUI (Event& event);
|
||||
|
||||
NSData* getProcessorState ();
|
||||
bool setProcessorState (NSData* data);
|
||||
|
||||
NSData* getControllerState ();
|
||||
bool setControllerState (NSData* data);
|
||||
|
||||
OBJ_METHODS (VST3Plugin, FObject)
|
||||
REFCOUNT_METHODS (FObject)
|
||||
DEFINE_INTERFACES
|
||||
DEF_INTERFACE (IComponentHandler)
|
||||
END_DEFINE_INTERFACES (FObject)
|
||||
protected:
|
||||
typedef std::map<uint32, uint32> NoteIDPitchMap;
|
||||
typedef uint32 ChannelAndCtrlNumber;
|
||||
typedef std::map<ChannelAndCtrlNumber, ParamID> MIDIControllerToParamIDMap;
|
||||
|
||||
void createProcessorAndController ();
|
||||
|
||||
void updateProcessContext (AudioIO* audioIO);
|
||||
MIDIControllerToParamIDMap createMIDIControllerToParamIDMap ();
|
||||
|
||||
// IComponentHandler
|
||||
tresult PLUGIN_API beginEdit (ParamID id) override;
|
||||
tresult PLUGIN_API performEdit (ParamID id, ParamValue valueNormalized) override;
|
||||
tresult PLUGIN_API endEdit (ParamID id) override;
|
||||
tresult PLUGIN_API restartComponent (int32 flags) override;
|
||||
|
||||
// IAudioIOProcessor
|
||||
void willStartAudio (AudioIO* audioIO) override;
|
||||
void didStopAudio (AudioIO* audioIO) override;
|
||||
void onMIDIEvent (UInt32 status, UInt32 data1, UInt32 data2, UInt32 sampleOffset,
|
||||
bool withinRealtimeThread) override;
|
||||
void process (const AudioTimeStamp* timeStamp, UInt32 busNumber, UInt32 numFrames,
|
||||
AudioBufferList* ioData, bool& outputIsSilence, AudioIO* audioIO) override;
|
||||
|
||||
// ITimerCallback
|
||||
void onTimer (Timer* timer) override;
|
||||
|
||||
IAudioProcessor* processor {nullptr};
|
||||
IEditController* editController {nullptr};
|
||||
Timer* timer {nullptr};
|
||||
|
||||
HostProcessData processData;
|
||||
ProcessContext processContext;
|
||||
ParameterChangeTransfer inputParamChangeTransfer;
|
||||
ParameterChangeTransfer outputParamChangeTransfer;
|
||||
ParameterChanges inputParamChanges;
|
||||
ParameterChanges outputParamChanges;
|
||||
EventList inputEvents;
|
||||
|
||||
NoteIDPitchMap noteIDPitchMap;
|
||||
std::atomic<int32> lastNodeID {0};
|
||||
|
||||
bool processing {false};
|
||||
|
||||
MIDIControllerToParamIDMap midiControllerToParamIDMap;
|
||||
|
||||
OneReaderOneWriter::RingBuffer<Event> uiScheduledEvents;
|
||||
|
||||
static ChannelAndCtrlNumber channelAndCtrlNumber (uint16 channel, CtrlNumber ctrler)
|
||||
{
|
||||
return (channel << 16) + ctrler;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace InterAppAudio
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
|
||||
/// \endcond
|
||||
@@ -0,0 +1,598 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VST3Plugin.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 "VST3Plugin.h"
|
||||
#import "HostApp.h"
|
||||
#import "public.sdk/source/vst/auwrapper/NSDataIBStream.h"
|
||||
#import "public.sdk/source/vst/hosting/hostclasses.h"
|
||||
#import "base/source/updatehandler.h"
|
||||
#import "pluginterfaces/base/ipluginbase.h"
|
||||
#import "pluginterfaces/vst/ivstinterappaudio.h"
|
||||
#import "pluginterfaces/vst/ivstmessage.h"
|
||||
#import "pluginterfaces/vst/ivstmidicontrollers.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
extern "C" {
|
||||
bool bundleEntry (CFBundleRef);
|
||||
bool bundleExit (void);
|
||||
}
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace InterAppAudio {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
__attribute__ ((constructor)) static void InitUpdateHandler ()
|
||||
{
|
||||
UpdateHandler::instance ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3Plugin::VST3Plugin ()
|
||||
{
|
||||
processData.processContext = &processContext;
|
||||
processData.inputParameterChanges = &inputParamChanges;
|
||||
processData.outputParameterChanges = &outputParamChanges;
|
||||
processData.inputEvents = &inputEvents;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3Plugin::~VST3Plugin ()
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool VST3Plugin::init ()
|
||||
{
|
||||
if (processor == nullptr && editController == nullptr)
|
||||
{
|
||||
::bundleEntry (CFBundleGetMainBundle ());
|
||||
createProcessorAndController ();
|
||||
}
|
||||
return processor && editController;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::createProcessorAndController ()
|
||||
{
|
||||
Steinberg::IPluginFactory* factory = GetPluginFactory ();
|
||||
if (factory == nullptr)
|
||||
return;
|
||||
IComponent* component = nullptr;
|
||||
PClassInfo classInfo;
|
||||
int32 classCount = factory->countClasses ();
|
||||
for (int32 i = 0; i < classCount; i++)
|
||||
{
|
||||
if (factory->getClassInfo (i, &classInfo) != kResultTrue)
|
||||
return;
|
||||
if (strcmp (classInfo.category, kVstAudioEffectClass) == 0)
|
||||
{
|
||||
if (factory->createInstance (classInfo.cid, IComponent::iid, (void**)&component) !=
|
||||
kResultTrue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (component)
|
||||
{
|
||||
if (component->initialize (InterAppAudioHostApp::instance ()->unknownCast ()) !=
|
||||
kResultTrue)
|
||||
{
|
||||
component->release ();
|
||||
return;
|
||||
}
|
||||
if (component->queryInterface (IEditController::iid, (void**)&editController) !=
|
||||
kResultTrue)
|
||||
{
|
||||
TUID controllerCID {};
|
||||
if (component->getControllerClassId (controllerCID) == kResultTrue)
|
||||
{
|
||||
if (factory->createInstance (controllerCID, IEditController::iid,
|
||||
(void**)&editController) != kResultTrue)
|
||||
return;
|
||||
editController->setComponentHandler (this);
|
||||
if (editController->initialize (
|
||||
InterAppAudioHostApp::instance ()->unknownCast ()) != kResultTrue)
|
||||
{
|
||||
component->release ();
|
||||
editController->release ();
|
||||
editController = nullptr;
|
||||
return;
|
||||
}
|
||||
auto compConnection = U::cast<IConnectionPoint> (component);
|
||||
auto ctrlerConnection = U::cast<IConnectionPoint> (editController);
|
||||
if (compConnection && ctrlerConnection)
|
||||
{
|
||||
compConnection->connect (ctrlerConnection);
|
||||
ctrlerConnection->connect (compConnection);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
component->release ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
component->queryInterface (IAudioProcessor::iid, (void**)&processor);
|
||||
if (processor == nullptr)
|
||||
{
|
||||
if (editController)
|
||||
{
|
||||
editController->release ();
|
||||
editController = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableData* data = [NSMutableData new];
|
||||
NSMutableDataIBStream state (data);
|
||||
if (component->getState (&state) == kResultTrue)
|
||||
{
|
||||
state.seek (0, IBStream::kIBSeekSet);
|
||||
editController->setComponentState (&state);
|
||||
}
|
||||
int32 paramCount = editController->getParameterCount ();
|
||||
inputParamChanges.setMaxParameters (paramCount);
|
||||
inputParamChangeTransfer.setMaxParameters (paramCount);
|
||||
outputParamChanges.setMaxParameters (paramCount);
|
||||
outputParamChangeTransfer.setMaxParameters (paramCount);
|
||||
midiControllerToParamIDMap = createMIDIControllerToParamIDMap ();
|
||||
uiScheduledEvents.resize (kMaxUIEvents);
|
||||
}
|
||||
component->release ();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3Plugin::MIDIControllerToParamIDMap VST3Plugin::createMIDIControllerToParamIDMap ()
|
||||
{
|
||||
MIDIControllerToParamIDMap newMap;
|
||||
|
||||
auto midiMapping = U::cast<IMidiMapping> (editController);
|
||||
if (midiMapping)
|
||||
{
|
||||
uint16 channelCount = 0;
|
||||
auto component = U::cast<IComponent> (processor);
|
||||
if (component)
|
||||
{
|
||||
int32 busCount = component->getBusCount (kEvent, kInput);
|
||||
if (busCount > 0)
|
||||
{
|
||||
BusInfo busInfo;
|
||||
if (component->getBusInfo (kEvent, kInput, 0, busInfo) == kResultTrue)
|
||||
{
|
||||
channelCount = busInfo.channelCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParamID paramID;
|
||||
for (int32 channel = 0; channel < channelCount; channel++)
|
||||
{
|
||||
for (CtrlNumber ctrler = 0; ctrler < kCountCtrlNumber; ctrler++)
|
||||
{
|
||||
if (midiMapping->getMidiControllerAssignment (0, channel, ctrler, paramID) ==
|
||||
kResultTrue)
|
||||
{
|
||||
newMap.insert (
|
||||
std::make_pair (channelAndCtrlNumber (channel, ctrler), paramID));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult VST3Plugin::scheduleEventFromUI (Event& event)
|
||||
{
|
||||
if (event.type == Event::kNoteOnEvent)
|
||||
event.noteOn.noteId = lastNodeID++;
|
||||
return uiScheduledEvents.push (event) ? kResultTrue : kResultFalse;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
NSData* VST3Plugin::getProcessorState ()
|
||||
{
|
||||
if (processor)
|
||||
{
|
||||
NSMutableData* data = [NSMutableData new];
|
||||
NSMutableDataIBStream state (data);
|
||||
auto comp = U::cast<IComponent> (processor);
|
||||
if (comp->getState (&state) == kResultTrue)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool VST3Plugin::setProcessorState (NSData* data)
|
||||
{
|
||||
if (editController && processor)
|
||||
{
|
||||
NSDataIBStream stream (data);
|
||||
auto comp = U::cast<IComponent> (processor);
|
||||
if (comp->setState (&stream) == kResultTrue)
|
||||
{
|
||||
stream.seek (0, IBStream::kIBSeekSet);
|
||||
editController->setComponentState (&stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
NSData* VST3Plugin::getControllerState ()
|
||||
{
|
||||
if (editController)
|
||||
{
|
||||
NSMutableData* data = [NSMutableData new];
|
||||
NSMutableDataIBStream state (data);
|
||||
if (editController->getState (&state) == kResultTrue)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool VST3Plugin::setControllerState (NSData* data)
|
||||
{
|
||||
if (editController)
|
||||
{
|
||||
NSDataIBStream stream (data);
|
||||
if (editController->setState (&stream) == kResultTrue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::willStartAudio (AudioIO* audioIO)
|
||||
{
|
||||
noteIDPitchMap.clear ();
|
||||
lastNodeID.store (0);
|
||||
|
||||
ProcessSetup setup;
|
||||
setup.processMode = kRealtime;
|
||||
setup.symbolicSampleSize = kSample32;
|
||||
setup.maxSamplesPerBlock = 4096; // TODO:
|
||||
setup.sampleRate = audioIO->getSampleRate ();
|
||||
processor->setupProcessing (setup);
|
||||
SpeakerArrangement inputs[1];
|
||||
SpeakerArrangement outputs[1];
|
||||
inputs[0] = SpeakerArr::kStereo;
|
||||
outputs[0] = SpeakerArr::kStereo;
|
||||
processor->setBusArrangements (inputs, 1, outputs, 1);
|
||||
|
||||
auto comp = U::cast<IComponent> (processor);
|
||||
comp->setActive (true);
|
||||
|
||||
processData.prepare (*comp, setup.maxSamplesPerBlock, setup.symbolicSampleSize);
|
||||
|
||||
auto iaaConnectionNotification = U::cast<IInterAppAudioConnectionNotification> (editController);
|
||||
if (iaaConnectionNotification)
|
||||
{
|
||||
iaaConnectionNotification->onInterAppAudioConnectionStateChange (
|
||||
audioIO->getInterAppAudioConnected () ? true : false);
|
||||
}
|
||||
|
||||
timer = Timer::create (this, 16);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::didStopAudio (AudioIO* audioIO)
|
||||
{
|
||||
processor->setProcessing (false);
|
||||
processing = false;
|
||||
|
||||
auto comp = U::cast<IComponent> (processor);
|
||||
comp->setActive (false);
|
||||
timer->release ();
|
||||
timer = nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::onMIDIEvent (UInt32 inStatus, UInt32 data1, UInt32 data2, UInt32 sampleOffset,
|
||||
bool withinRealtimeThread)
|
||||
{
|
||||
Event e = {};
|
||||
e.flags = Event::kIsLive;
|
||||
|
||||
uint16 status = inStatus & 0xF0;
|
||||
uint16 channel = inStatus & 0x0F;
|
||||
if (status == 0x90 && data2 != 0) // note on
|
||||
{
|
||||
auto noteID = noteIDPitchMap.find ((channel << 8) + data1);
|
||||
if (noteID != noteIDPitchMap.end ())
|
||||
{
|
||||
// for now, we just turn off the old note on
|
||||
Event e2 = {};
|
||||
e2.type = Event::kNoteOffEvent;
|
||||
e2.noteOff.noteId = noteID->second;
|
||||
e2.noteOff.channel = channel;
|
||||
e2.noteOff.pitch = data1;
|
||||
e2.noteOff.velocity = (float)data2 / 128.f;
|
||||
e2.sampleOffset = 0;
|
||||
inputEvents.addEvent (e2);
|
||||
noteIDPitchMap.erase (noteID);
|
||||
}
|
||||
e.type = Event::kNoteOnEvent;
|
||||
e.noteOn.channel = channel;
|
||||
e.noteOn.pitch = data1;
|
||||
e.noteOn.velocity = (float)data2 / 128.f;
|
||||
e.noteOn.length = -1;
|
||||
e.sampleOffset = sampleOffset;
|
||||
if (withinRealtimeThread)
|
||||
{
|
||||
e.noteOn.noteId = lastNodeID++;
|
||||
inputEvents.addEvent (e);
|
||||
noteIDPitchMap.insert (std::make_pair ((channel << 8) + data1, e.noteOn.noteId));
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduleEventFromUI (e);
|
||||
}
|
||||
}
|
||||
else if (status == 0x80 || (status == 0x90 && data2 == 0)) // note off
|
||||
{
|
||||
auto noteID = noteIDPitchMap.find ((channel << 8) + data1);
|
||||
if (noteID != noteIDPitchMap.end ())
|
||||
{
|
||||
e.type = Event::kNoteOffEvent;
|
||||
e.noteOff.noteId = noteID->second;
|
||||
e.noteOff.channel = channel;
|
||||
e.noteOff.pitch = data1;
|
||||
e.noteOff.velocity = (float)data2 / 128.f;
|
||||
e.sampleOffset = sampleOffset;
|
||||
if (withinRealtimeThread)
|
||||
{
|
||||
inputEvents.addEvent (e);
|
||||
noteIDPitchMap.erase (noteID);
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduleEventFromUI (e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog (@"NoteID not found:%d", (unsigned int)data1);
|
||||
}
|
||||
}
|
||||
else if (status == 0xb0 && data1 < kAfterTouch) // controller
|
||||
{
|
||||
auto it = midiControllerToParamIDMap.find (channelAndCtrlNumber (channel, data1));
|
||||
if (it != midiControllerToParamIDMap.end ())
|
||||
{
|
||||
ParamValue value = (ParamValue)data2 / 128.;
|
||||
if (withinRealtimeThread)
|
||||
{
|
||||
int32 index;
|
||||
IParamValueQueue* queue = inputParamChanges.addParameterData (it->second, index);
|
||||
if (queue)
|
||||
{
|
||||
queue->addPoint (sampleOffset, value, index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inputParamChangeTransfer.addChange (it->second, value, sampleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (status == 0xe0) // pitch bend
|
||||
{
|
||||
auto it = midiControllerToParamIDMap.find (channelAndCtrlNumber (channel, kPitchBend));
|
||||
if (it != midiControllerToParamIDMap.end ())
|
||||
{
|
||||
uint16 _14bit;
|
||||
_14bit = (uint16)data2;
|
||||
_14bit <<= 7;
|
||||
_14bit |= (uint16)data1;
|
||||
ParamValue value = (double)_14bit / (double)0x3fff;
|
||||
|
||||
if (withinRealtimeThread)
|
||||
{
|
||||
int32 index;
|
||||
IParamValueQueue* queue = inputParamChanges.addParameterData (it->second, index);
|
||||
if (queue)
|
||||
{
|
||||
queue->addPoint (sampleOffset, value, index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inputParamChangeTransfer.addChange (it->second, value, sampleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (status == 0xd0) // aftertouch
|
||||
{
|
||||
auto it = midiControllerToParamIDMap.find (channelAndCtrlNumber (channel, kAfterTouch));
|
||||
if (it != midiControllerToParamIDMap.end ())
|
||||
{
|
||||
ParamValue value = (ParamValue)data1 / 128.;
|
||||
if (withinRealtimeThread)
|
||||
{
|
||||
int32 index;
|
||||
IParamValueQueue* queue = inputParamChanges.addParameterData (it->second, index);
|
||||
if (queue)
|
||||
{
|
||||
queue->addPoint (sampleOffset, value, index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inputParamChangeTransfer.addChange (it->second, value, sampleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::process (const AudioTimeStamp* timeStamp, UInt32 busNumber, UInt32 numFrames,
|
||||
AudioBufferList* ioData, bool& outputIsSilence, AudioIO* audioIO)
|
||||
{
|
||||
if (processing == false)
|
||||
{
|
||||
processor->setProcessing (true);
|
||||
processing = true;
|
||||
}
|
||||
updateProcessContext (audioIO);
|
||||
if (timeStamp)
|
||||
processContext.systemTime = timeStamp->mHostTime;
|
||||
|
||||
// TODO: silence state update
|
||||
for (UInt32 i = 0; i < ioData->mNumberBuffers; i++)
|
||||
{
|
||||
processData.setChannelBuffer (kInput, 0, i, (float*)ioData->mBuffers[i].mData);
|
||||
processData.setChannelBuffer (kOutput, 0, i, (float*)ioData->mBuffers[i].mData);
|
||||
}
|
||||
|
||||
Event e;
|
||||
while (uiScheduledEvents.pop (e))
|
||||
{
|
||||
inputEvents.addEvent (e);
|
||||
if (e.type == Event::kNoteOnEvent)
|
||||
{
|
||||
auto noteID = noteIDPitchMap.find ((e.noteOn.channel << 8) + e.noteOn.pitch);
|
||||
if (noteID == noteIDPitchMap.end ())
|
||||
noteIDPitchMap.insert (
|
||||
std::make_pair ((e.noteOn.channel << 8) + e.noteOn.pitch, e.noteOn.noteId));
|
||||
}
|
||||
}
|
||||
|
||||
processData.numSamples = numFrames;
|
||||
inputParamChangeTransfer.transferChangesTo (inputParamChanges);
|
||||
if (processor->process (processData) == kResultTrue)
|
||||
{
|
||||
inputParamChanges.clearQueue ();
|
||||
outputParamChangeTransfer.transferChangesFrom (outputParamChanges);
|
||||
outputParamChanges.clearQueue ();
|
||||
inputEvents.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::updateProcessContext (AudioIO* audioIO)
|
||||
{
|
||||
memset (&processContext, 0, sizeof (ProcessContext));
|
||||
processContext.sampleRate = audioIO->getSampleRate ();
|
||||
Float64 beat = 0., tempo = 0.;
|
||||
if (audioIO->getBeatAndTempo (beat, tempo))
|
||||
{
|
||||
processContext.state |=
|
||||
ProcessContext::kTempoValid | ProcessContext::kProjectTimeMusicValid;
|
||||
processContext.tempo = tempo;
|
||||
processContext.projectTimeMusic = beat;
|
||||
}
|
||||
else
|
||||
{
|
||||
processContext.state |= ProcessContext::kTempoValid;
|
||||
processContext.tempo = 120.;
|
||||
}
|
||||
UInt32 deltaSampleOffsetToNextBeat = 0;
|
||||
Float32 timeSigNumerator = 0;
|
||||
UInt32 timeSigDenominator = 0;
|
||||
Float64 currentMeasureDownBeat = 0;
|
||||
if (audioIO->getMusicalTimeLocation (deltaSampleOffsetToNextBeat, timeSigNumerator,
|
||||
timeSigDenominator, currentMeasureDownBeat))
|
||||
{
|
||||
processContext.state |= ProcessContext::kTimeSigValid | ProcessContext::kBarPositionValid |
|
||||
ProcessContext::kClockValid;
|
||||
processContext.timeSigNumerator = timeSigNumerator;
|
||||
processContext.timeSigDenominator = timeSigDenominator;
|
||||
processContext.samplesToNextClock = deltaSampleOffsetToNextBeat;
|
||||
processContext.barPositionMusic = currentMeasureDownBeat;
|
||||
}
|
||||
Boolean isPlaying;
|
||||
Boolean isRecording;
|
||||
Boolean transportStateChanged;
|
||||
Float64 currentSampleInTimeLine;
|
||||
Boolean isCycling;
|
||||
Float64 cycleStartBeat;
|
||||
Float64 cycleEndBeat;
|
||||
if (audioIO->getTransportState (isPlaying, isRecording, transportStateChanged,
|
||||
currentSampleInTimeLine, isCycling, cycleStartBeat,
|
||||
cycleEndBeat))
|
||||
{
|
||||
processContext.state |= ProcessContext::kCycleValid;
|
||||
processContext.cycleStartMusic = cycleStartBeat;
|
||||
processContext.cycleEndMusic = cycleEndBeat;
|
||||
processContext.projectTimeSamples = currentSampleInTimeLine;
|
||||
if (isPlaying)
|
||||
processContext.state |= ProcessContext::kPlaying;
|
||||
if (isCycling)
|
||||
processContext.state |= ProcessContext::kCycleActive;
|
||||
if (isRecording)
|
||||
processContext.state |= ProcessContext::kRecording;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3Plugin::beginEdit (ParamID id)
|
||||
{
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3Plugin::performEdit (ParamID id, ParamValue valueNormalized)
|
||||
{
|
||||
inputParamChangeTransfer.addChange (id, valueNormalized, 0);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3Plugin::endEdit (ParamID id)
|
||||
{
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3Plugin::restartComponent (int32 flags)
|
||||
{
|
||||
tresult result = kNotImplemented;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void VST3Plugin::onTimer (Timer* timer)
|
||||
{
|
||||
ParamID paramID;
|
||||
ParamValue paramValue;
|
||||
int32 sampleOffset;
|
||||
while (outputParamChangeTransfer.getNextChange (paramID, paramValue, sampleOffset))
|
||||
{
|
||||
editController->setParamNormalized (paramID, paramValue);
|
||||
}
|
||||
UpdateHandler::instance ()->triggerDeferedUpdates ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VSTInterAppAudioAppDelegateBase.h
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Base UIApplicationDelegate class.
|
||||
* This class provides the base handling of the audio engine, plug-in and plug-in editor\n
|
||||
* You should subclass it for customization\n
|
||||
* Make sure to call the methods of this class if you override one in your subclass !
|
||||
*/
|
||||
//------------------------------------------------------------------------
|
||||
@interface VSTInterAppAudioAppDelegateBase : UIResponder <UIApplicationDelegate>
|
||||
//------------------------------------------------------------------------
|
||||
@property (strong, nonatomic) UIWindow* window;
|
||||
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
|
||||
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder;
|
||||
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder;
|
||||
- (void)applicationDidBecomeActive:(UIApplication*)application;
|
||||
- (void)applicationWillResignActive:(UIApplication*)application;
|
||||
|
||||
@end
|
||||
Vendored
+193
@@ -0,0 +1,193 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/interappaudio/VSTInterAppAudioAppDelegateBase.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 "VSTInterAppAudioAppDelegateBase.h"
|
||||
#import "public.sdk/source/vst/interappaudio/AudioIO.h"
|
||||
#import "public.sdk/source/vst/interappaudio/HostApp.h"
|
||||
#import "public.sdk/source/vst/interappaudio/MidiIO.h"
|
||||
#import "public.sdk/source/vst/interappaudio/VST3Editor.h"
|
||||
#import "public.sdk/source/vst/interappaudio/VST3Plugin.h"
|
||||
|
||||
using namespace Steinberg::Vst::InterAppAudio;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static OSType fourCharCodeToOSType (NSString* inCode)
|
||||
{
|
||||
OSType rval = 0;
|
||||
NSData* data = [inCode dataUsingEncoding:NSMacOSRomanStringEncoding];
|
||||
[data getBytes:&rval length:sizeof (rval)];
|
||||
HTONL (rval);
|
||||
return rval;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@interface VSTInterAppAudioAppDelegateBase ()
|
||||
//------------------------------------------------------------------------
|
||||
{
|
||||
VST3Plugin plugin;
|
||||
VST3Editor editor;
|
||||
|
||||
BOOL audioIOInitialized;
|
||||
}
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@implementation VSTInterAppAudioAppDelegateBase
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)initAudioIO
|
||||
{
|
||||
id auArray = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AudioComponents"];
|
||||
if (auArray)
|
||||
{
|
||||
id desc = [auArray objectAtIndex:0];
|
||||
if (desc)
|
||||
{
|
||||
NSString* typeStr = [desc objectForKey:@"type"];
|
||||
NSString* subtypeStr = [desc objectForKey:@"subtype"];
|
||||
NSString* manufacturerStr = [desc objectForKey:@"manufacturer"];
|
||||
NSString* nameStr = [desc objectForKey:@"name"];
|
||||
if (typeStr && subtypeStr && manufacturerStr && nameStr)
|
||||
{
|
||||
OSType type = fourCharCodeToOSType (typeStr);
|
||||
OSType subtype = fourCharCodeToOSType (subtypeStr);
|
||||
OSType manufacturer = fourCharCodeToOSType (manufacturerStr);
|
||||
AudioIO* audioIO = AudioIO::instance ();
|
||||
if (audioIO->init (type, subtype, manufacturer, (__bridge CFStringRef)nameStr) ==
|
||||
Steinberg::kResultTrue)
|
||||
{
|
||||
if (plugin.init ())
|
||||
{
|
||||
InterAppAudioHostApp::instance ()->setPlugin (&plugin);
|
||||
audioIO->addProcessor (&plugin);
|
||||
audioIOInitialized = YES;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)createUI
|
||||
{
|
||||
if (audioIOInitialized)
|
||||
{
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
self.window = [UIWindow new];
|
||||
self.window.backgroundColor = [UIColor redColor];
|
||||
CGRect screenSize = self.window.bounds;
|
||||
|
||||
if (editor.init (screenSize))
|
||||
{
|
||||
self.window.rootViewController = editor.getViewController ();
|
||||
[self.window makeKeyAndVisible];
|
||||
if (editor.attach (plugin.getEditController ()) == false)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)savePluginState:(NSCoder*)coder
|
||||
{
|
||||
NSData* processorState = plugin.getProcessorState ();
|
||||
NSData* controllerState = plugin.getControllerState ();
|
||||
if (processorState)
|
||||
[coder encodeObject:processorState forKey:@"VST3ProcessorState"];
|
||||
if (controllerState)
|
||||
[coder encodeObject:controllerState forKey:@"VST3ControllerState"];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)restorePluginState:(NSCoder*)coder
|
||||
{
|
||||
NSData* processorState = [coder decodeObjectForKey:@"VST3ProcessorState"];
|
||||
if (processorState)
|
||||
{
|
||||
plugin.setProcessorState (processorState);
|
||||
}
|
||||
NSData* controllerState = [coder decodeObjectForKey:@"VST3ControllerState"];
|
||||
if (controllerState)
|
||||
{
|
||||
plugin.setControllerState (controllerState);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// UIApplicationDelegate methods
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
|
||||
{
|
||||
return [self initAudioIO];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
|
||||
{
|
||||
BOOL result = [self createUI];
|
||||
if (result)
|
||||
{
|
||||
AudioIO::instance ()->start ();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder
|
||||
{
|
||||
[self savePluginState:coder];
|
||||
[coder encodeBool:MidiIO::instance ().isEnabled () forKey:@"MIDI Enabled"];
|
||||
return YES;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder
|
||||
{
|
||||
[self restorePluginState:coder];
|
||||
BOOL midiEnabled = [coder decodeBoolForKey:@"MIDI Enabled"];
|
||||
MidiIO::instance ().setEnabled (midiEnabled);
|
||||
return YES;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)applicationDidBecomeActive:(UIApplication*)application
|
||||
{
|
||||
AudioIO* audioIO = AudioIO::instance ();
|
||||
audioIO->start ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
- (void)applicationWillResignActive:(UIApplication*)application
|
||||
{
|
||||
AudioIO* audioIO = AudioIO::instance ();
|
||||
if (audioIO->getInterAppAudioConnected () == false && MidiIO::instance ().isEnabled () == false)
|
||||
{
|
||||
audioIO->stop ();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user