599 lines
17 KiB
Plaintext
599 lines
17 KiB
Plaintext
//-----------------------------------------------------------------------------
|
|
// 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 ();
|
|
}
|
|
}
|
|
}
|
|
}
|