//----------------------------------------------------------------------------- // 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 //------------------------------------------------------------------------ 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 (component); auto ctrlerConnection = U::cast (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 (editController); if (midiMapping) { uint16 channelCount = 0; auto component = U::cast (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 (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 (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 (processor); comp->setActive (true); processData.prepare (*comp, setup.maxSamplesPerBlock, setup.symbolicSampleSize); auto iaaConnectionNotification = U::cast (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 (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 (); } } } }