//------------------------------------------------------------------------ // Flags : clang-format auto // Project : VST SDK // // Category : Helpers // Filename : public.sdk/source/vst/aaxwrapper/aaxwrapper.cpp // Created by : Steinberg, 08/2017 // Description : VST 3 -> AAX Wrapper // //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- /// \cond ignore #include "aaxwrapper.h" #include "AAX.h" #include "AAX_Assert.h" #include "AAX_Errors.h" #include "AAX_ICollection.h" #include "AAX_IComponentDescriptor.h" #include "AAX_IEffectDescriptor.h" #include "AAX_IMIDINode.h" #include "AAX_IPropertyMap.h" #include "AAX_Version.h" static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p3p2_REVISION, "VST3 SDK requires AAX SDK version 2.3.2 or higher"); #include "aaxwrapper_description.h" #include "aaxwrapper_gui.h" #include "aaxwrapper_parameters.h" #include "base/thread/include/fcondition.h" #include "pluginterfaces/base/funknownimpl.h" #define USE_TRACE 1 #if USE_TRACE #define HAPI AAX_eTracePriorityHost_Normal #define HLOG AAX_TRACE #else #define HAPI 0 #if SMTG_OS_WINDOWS #define HLOG __noop #else #define HLOG(...) \ do \ { \ } while (false) #endif #endif #if SMTG_OS_WINDOWS #include #define getCurrentThread() ((void*)(size_t)GetCurrentThreadId ()) #else #include #define getCurrentThread() (pthread_self ()) #endif using namespace Steinberg; using namespace Steinberg::Vst; using namespace Steinberg::Base::Thread; //------------------------------------------------------------------------ class AAXEditorWrapper : public BaseEditorWrapper { public: AAXEditorWrapper (AAXWrapper* wrapper, IEditController* controller) : BaseEditorWrapper (controller), mAAXWrapper (wrapper) { } tresult PLUGIN_API resizeView (IPlugView* view, ViewRect* newSize) SMTG_OVERRIDE; private: AAXWrapper* mAAXWrapper; }; //------------------------------------------------------------------------ tresult PLUGIN_API AAXEditorWrapper::resizeView (IPlugView* view, ViewRect* newSize) { tresult result = kResultFalse; if (view && newSize) { if (mAAXWrapper->_sizeWindow (newSize->getWidth (), newSize->getHeight ())) { result = view->onSize (newSize); } } return result; } //------------------------------------------------------------------------ //------------------------------------------------------------------------ AAXWrapper::AAXWrapper (BaseWrapper::SVST3Config& config, AAXWrapper_Parameters* p, AAX_Plugin_Desc* desc) : BaseWrapper (config), mAAXParams (p), mPluginDesc (desc) { HLOG (HAPI, "%s", __FUNCTION__); mBlockSize = 1024; // never explicitly changed by Pro Tools, so assume the maximum mUseExportedBypass = true; mUseIncIndex = false; mainThread = getCurrentThread (); // must be in lock step with DescribeAlgorithmComponent int32 idx = idxBufferSize + 1; if (desc->mInputChannels || desc->mOutputChannels) idxInputChannels = idx++; if (desc->mOutputChannels) idxOutputChannels = idx++; if (desc->mSideChainInputChannels) idxSideChainInputChannels = idx++; if (desc->mMIDIports) { for (AAX_MIDI_Desc* mdesc = desc->mMIDIports; mdesc->mName; mdesc++) mCountMIDIports++; if (mCountMIDIports > 0) { idxMidiPorts = idx; idx += mCountMIDIports; } } int32 numAuxOutputs = 0; mAAXOutputs = static_cast (desc->mOutputChannels); if (desc->mAuxOutputChannels) { for (AAX_Aux_Desc *adesc = desc->mAuxOutputChannels; adesc->mName; adesc++, numAuxOutputs++) mAAXOutputs += adesc->mChannels < 0 ? desc->mOutputChannels : adesc->mChannels; if (numAuxOutputs > 0) { idxAuxOutputs = idx; idx += numAuxOutputs; } } if (desc->mMeters) { idxMeters = idx++; for (auto mdesc = desc->mMeters; mdesc->mName; mdesc++) mCntMeters++; mMeterIds.reset (NEW Steinberg::Vst::ParamID (mCntMeters)); mCntMeters = 0; for (auto mdesc = desc->mMeters; mdesc->mName; mdesc++) mMeterIds[mCntMeters++] = static_cast (mdesc->mID); } numDataPointers = idx; } //------------------------------------------------------------------------ AAXWrapper::~AAXWrapper () { HLOG (HAPI, "%s", __FUNCTION__); } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::getName (String128 name) { String str ("AAXWrapper"); str.copyTo16 (name, 0, 127); return kResultTrue; } //------------------------------------------------------------------------ Vst::ParamID getVstParamID (AAX_CParamID aaxid) { if (aaxid[0] != 'p') return kNoParamId; long id; if (sscanf (aaxid + 1, "%lx", &id) != 1) return kNoParamId; return static_cast (id); } //------------------------------------------------------------------------ // IComponentHandler //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::beginEdit (ParamID tag) { HLOG (HAPI, "%s(tag=%x)", __FUNCTION__, tag); AAX_CID aaxid (tag); mAAXParams->TouchParameter (aaxid); return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::performEdit (ParamID tag, ParamValue valueNormalized) { HLOG (HAPI, "%s(tag=%x, value=%lf)", __FUNCTION__, tag, valueNormalized); AAX_CID aaxid (tag); mAAXParams->SetParameterNormalizedValue (aaxid, valueNormalized); return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::endEdit (ParamID tag) { HLOG (HAPI, "%s(tag=%x)", __FUNCTION__, tag); AAX_CID aaxid (tag); mAAXParams->ReleaseParameter (aaxid); return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::setDirty (TBool state) { mAAXParams->setDirty (state != 0); return kResultOk; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::requestOpenEditor (FIDString /*name*/) { return kResultFalse; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::startGroupEdit () { return kResultFalse; } //------------------------------------------------------------------------ tresult PLUGIN_API AAXWrapper::finishGroupEdit () { return kResultFalse; } //------------------------------------------------------------------------ bool AAXWrapper::init () { bool res = BaseWrapper::init (); if (mController && BaseEditorWrapper::hasEditor (mController)) _setEditor (NEW AAXEditorWrapper (this, mController)); return res; } //------------------------------------------------------------------------ void AAXWrapper::setupProcessTimeInfo () { mProcessContext.state = 0; mProcessContext.sampleRate = mSampleRate; if (AAX_ITransport* transport = mAAXParams->Transport ()) { int64_t splPos, ppqPos, loopStart, loopEnd; bool playing, looping; if (transport->GetCurrentNativeSampleLocation (&splPos) == AAX_SUCCESS) mProcessContext.projectTimeSamples = (TSamples)splPos; if (transport->GetCurrentTickPosition (&ppqPos) == AAX_SUCCESS) { mProcessContext.projectTimeMusic = (double)ppqPos / 960000.0; mProcessContext.state |= ProcessContext::kProjectTimeMusicValid; } else mProcessContext.projectTimeMusic = 0; if (transport->GetCurrentTempo (&mProcessContext.tempo) == AAX_SUCCESS) mProcessContext.state |= ProcessContext::kTempoValid; if (transport->GetCurrentLoopPosition (&looping, &loopStart, &loopEnd) == AAX_SUCCESS) { mProcessContext.cycleStartMusic = (double)loopStart / 960000.0; mProcessContext.cycleEndMusic = (double)loopEnd / 960000.0; mProcessContext.state |= ProcessContext::kCycleValid; if (looping) mProcessContext.state |= ProcessContext::kCycleActive; } if (transport->IsTransportPlaying (&playing) == AAX_SUCCESS) { mProcessContext.state |= (playing ? ProcessContext::kPlaying : 0); } // workaround ppqPos not updating for every 2nd audio blocks @ 96 kHz (and more for higher // frequencies) // and while the UI is frozen, e.g. during save static const int32 playflags = ProcessContext::kPlaying | ProcessContext::kProjectTimeMusicValid | ProcessContext::kTempoValid; if ((playflags & mProcessContext.state) == playflags && mSampleRate != 0) { TQuarterNotes ppq = mProcessContext.projectTimeMusic; if (ppq == mLastPpqPos && mLastPpqPos != 0 && mNextPpqPos != 0) { TQuarterNotes nextppq = mNextPpqPos; if (mProcessContext.state & ProcessContext::kCycleActive) if (nextppq >= mProcessContext.cycleEndMusic) nextppq += mProcessContext.cycleStartMusic - mProcessContext.cycleEndMusic; mProcessContext.projectTimeMusic = nextppq; } mLastPpqPos = ppq; mNextPpqPos = mProcessContext.projectTimeMusic + mProcessContext.tempo / 60 * mProcessData.numSamples / mSampleRate; } else { mLastPpqPos = mNextPpqPos = 0; } int32_t num, den; if (transport->GetCurrentMeter (&num, &den) == AAX_SUCCESS) { mProcessContext.timeSigNumerator = num; mProcessContext.timeSigDenominator = den; mProcessContext.state |= ProcessContext::kTimeSigValid; } else mProcessContext.timeSigNumerator = mProcessContext.timeSigDenominator = 4; AAX_EFrameRate frameRate; int32_t offset; if (transport->GetTimeCodeInfo (&frameRate, &offset) == AAX_SUCCESS) { mProcessContext.state |= ProcessContext::kSmpteValid; mProcessContext.smpteOffsetSubframes = offset; switch (frameRate) { case AAX_eFrameRate_24Frame: mProcessContext.frameRate.framesPerSecond = 24; break; case AAX_eFrameRate_25Frame: mProcessContext.frameRate.framesPerSecond = 25; break; case AAX_eFrameRate_2997NonDrop: mProcessContext.frameRate.framesPerSecond = 30; mProcessContext.frameRate.flags = FrameRate::kPullDownRate; break; case AAX_eFrameRate_2997DropFrame: mProcessContext.frameRate.framesPerSecond = 30; mProcessContext.frameRate.flags = FrameRate::kDropRate | FrameRate::kPullDownRate; break; case AAX_eFrameRate_30NonDrop: mProcessContext.frameRate.framesPerSecond = 30; break; case AAX_eFrameRate_30DropFrame: mProcessContext.frameRate.framesPerSecond = 30; mProcessContext.frameRate.flags = FrameRate::kDropRate; break; case AAX_eFrameRate_23976: mProcessContext.frameRate.framesPerSecond = 24; mProcessContext.frameRate.flags = FrameRate::kPullDownRate; break; case AAX_eFrameRate_Undeclared: default: mProcessContext.state &= ~ProcessContext::kSmpteValid; } } mProcessData.processContext = &mProcessContext; } else mProcessData.processContext = nullptr; } //------------------------------------------------------------------------ bool AAXWrapper::_sizeWindow (int32 width, int32 height) { HLOG (HAPI, "%s(width=%x, height=%x)", __FUNCTION__, width, height); AAX_ASSERT (mainThread == getCurrentThread ()); if (!mAAXGUI) return false; AAX_Point size (static_cast (height), static_cast (width)); return mAAXGUI->setWindowSize (size); } //------------------------------------------------------------------------ struct AAXWrapper::GetChunkMessage : public FCondition { void* mData = nullptr; int32 mDataSize = 0; int32 mResult = 0; }; //------------------------------------------------------------------------ int32 AAXWrapper::_getChunk (void** data, bool isPreset) { if (mWantsSetChunk) { // isPreset is always false for AAX, so we can ignore it *data = mChunk.getData (); return static_cast (mChunk.getSize ()); } if (mainThread == getCurrentThread ()) return BaseWrapper::_getChunk (data, isPreset); auto* msg = NEW GetChunkMessage; { FGuard guard (msgQueueLock); msgQueue.push_back (msg); } msg->wait (); *data = msg->mData; return msg->mResult; } //------------------------------------------------------------------------ int32 AAXWrapper::_setChunk (void* data, int32 byteSize, bool isPreset) { if (mainThread == getCurrentThread ()) return BaseWrapper::_setChunk (data, byteSize, isPreset); FGuard guard (msgQueueLock); mChunk.setSize (byteSize); memcpy (mChunk.getData (), data, static_cast (byteSize)); mWantsSetChunk = true; mWantsSetChunkIsPreset = isPreset; return 0; } //------------------------------------------------------------------------ void AAXWrapper::onTimer (Timer* timer) { BaseWrapper::onTimer (timer); AAX_ASSERT (mainThread == getCurrentThread ()); if (mWantsSetChunk && !mSettingChunk) { mSettingChunk = true; FGuard guard (msgQueueLock); BaseWrapper::_setChunk (mChunk.getData (), static_cast (mChunk.getSize ()), mWantsSetChunkIsPreset); mWantsSetChunk = false; mSettingChunk = false; mWantsSetChunkIsPreset = false; if (mPresetChanged) { int32_t numParams; if (mAAXParams->GetNumberOfParameters (&numParams) == AAX_SUCCESS) { AAX_CID bypassId (mBypassParameterID); for (auto i = 0; i < numParams; i++) { AAX_CString id; if (mAAXParams->GetParameterIDFromIndex (i, &id) == AAX_SUCCESS) { if (id == (const char*)bypassId) { mAAXParams->SetParameterNormalizedValue (id.CString (), mBypassBeforePresetChanged); } else { double value; if (mAAXParams->GetParameterNormalizedValue (id.CString (), &value) == AAX_SUCCESS) { mAAXParams->SetParameterNormalizedValue (id.CString (), value); } } } } } mPresetChanged = false; } } updateActiveOutputState (); while (!msgQueue.empty ()) { GetChunkMessage* msg = nullptr; { FGuard guard (msgQueueLock); if (!msgQueue.empty ()) { msg = msgQueue.front (); msgQueue.pop_front (); } } if (msg) { msg->mResult = BaseWrapper::_getChunk (&msg->mData, false); msg->signal (); } } } //------------------------------------------------------------------------ int32 AAXWrapper::getParameterInfo (AAX_CParamID aaxId, Vst::ParameterInfo& paramInfo) { HLOG (HAPI, "%s(id=%s)", __FUNCTION__, aaxId); Vst::ParamID id = getVstParamID (aaxId); if (id == -1) return AAX_ERROR_INVALID_PARAMETER_ID; std::map::iterator iter = mParamIndexMap.find (id); if (iter == mParamIndexMap.end ()) return AAX_ERROR_INVALID_PARAMETER_ID; if (mController->getParameterInfo (iter->second, paramInfo) != kResultTrue) return AAX_ERROR_INVALID_PARAMETER_ID; return AAX_SUCCESS; } //------------------------------------------------------------------------ bool AAXWrapper::generatePageTables (const char* outputFile) { #if 0 && DEVELOPMENT FILE* fh = fopen (outputFile, "w"); if (!fh) return false; fprintf (fh, "\n" "\n" "\t\n"); AAX_Effect_Desc* effDesc = AAXWrapper_GetDescription (); for (const AAX_Plugin_Desc* pdesc = effDesc->mPluginDesc; pdesc->mEffectID; pdesc++) { uint32_t manId = effDesc->mManufacturerID; SWAP_32 (manId); uint32_t prodId = effDesc->mProductID; SWAP_32 (prodId); uint32_t plugId = pdesc->mPlugInIDNative; SWAP_32 (plugId); fprintf (fh, "\t\t\n" "\t\t\t%s\n" "\t\t\tPageTable 1\n" "\t\t", &manId, &prodId, &plugId, pdesc->mName); fprintf (fh, "\t\t\t\n"); int32 cntParams = controller->getParameterCount (); for (int32 i = 0; i < cntParams; i++) { Vst::ParameterInfo info; if (controller->getParameterInfo (i, info) == kResultTrue && (info.flags & ParameterInfo::kCanAutomate)) { AAX_CID id (info.id); String stitle (info.shortTitle); fprintf (fh, "\t\t\t\t%s\n", i + 1, (const char*) id, stitle.text8 ()); } } fprintf (fh, "\t\t\t\n"); } fprintf (fh, "\t\n"); fprintf (fh, "\t\n"); fprintf (fh, "\t\n" "\t\t\n" "\t\t\t\n"); for (const AAX_Plugin_Desc* pdesc = effDesc->mPluginDesc; pdesc->mEffectID; pdesc++) { uint32_t manId = effDesc->mManufacturerID; SWAP_32 (manId); uint32_t prodId = effDesc->mProductID; SWAP_32 (prodId); uint32_t plugId = pdesc->mPlugInIDNative; SWAP_32 (plugId); fprintf (fh, "\t\t\t\t\n" "\t\t\t\t\t%s\n" "\t\t\t\t\n", &manId, &prodId, &plugId, pdesc->mName); } fprintf (fh, "\t\t\t\n" "\t\t\n" "\t\n"); fprintf (fh, "\n"); fclose (fh); #else outputFile; #endif return true; } //------------------------------------------------------------------------ static int32 getChannelsStem (int32 channels) { switch (channels) { case 1: return AAX_eStemFormat_Mono; case 2: return AAX_eStemFormat_Stereo; case 3: return AAX_eStemFormat_LCR; case 4: return AAX_eStemFormat_Ambi_1_ACN; // AAX_eStemFormat_Quad case 5: return AAX_eStemFormat_5_0; case 6: return AAX_eStemFormat_5_1; // AAX_eStemFormat_6_0 case 7: return AAX_eStemFormat_6_1; // AAX_eStemFormat_7_0_DTS case 8: return AAX_eStemFormat_7_1_DTS; case 9: return AAX_eStemFormat_Ambi_2_ACN; // AAX_eStemFormat_7_0_2; case 10: return AAX_eStemFormat_7_1_2; case 16: return AAX_eStemFormat_Ambi_3_ACN; } return AAX_eStemFormat_None; } //------------------------------------------------------------------------------- static SpeakerArrangement numChannelsToSpeakerArrangement (int32 numChannels) { switch (numChannels) { case 1: return SpeakerArr::kMono; case 2: return SpeakerArr::kStereo; case 3: return SpeakerArr::k30Cine; case 4: return SpeakerArr::kAmbi1stOrderACN; case 5: return SpeakerArr::k50; case 6: return SpeakerArr::k51; case 7: return SpeakerArr::k61Cine; case 8: return SpeakerArr::k71Cine; case 9: return SpeakerArr::kAmbi2cdOrderACN; case 10: return SpeakerArr::k71_2; case 16: return SpeakerArr::kAmbi3rdOrderACN; } return 0; } //------------------------------------------------------------------------ // static //------------------------------------------------------------------------ AAXWrapper* AAXWrapper::create (IPluginFactory* factory, const TUID vst3ComponentID, AAX_Plugin_Desc* desc, AAXWrapper_Parameters* params) { // mostly a copy of BaseWrapper::create if (!factory) return nullptr; BaseWrapper::SVST3Config config; config.processor = nullptr; config.factory = factory; FReleaser factoryReleaser (factory); factory->createInstance (vst3ComponentID, IAudioProcessor::iid, (void**)&config.processor); if (!config.processor) return nullptr; config.controller = nullptr; if (config.processor->queryInterface (IEditController::iid, (void**)&config.controller) != kResultTrue) { if (auto component = U::cast (config.processor)) { TUID editorCID; if (component->getControllerClassId (editorCID) == kResultTrue) { factory->createInstance (editorCID, IEditController::iid, (void**)&config.controller); } } } config.vst3ComponentID = FUID::fromTUID (vst3ComponentID); auto* wrapper = NEW AAXWrapper (config, params, desc); if (wrapper->init () == false || wrapper->setupBusArrangements (desc) != kResultOk) { wrapper->release (); return nullptr; } wrapper->setupBuses (); // again to adjust to changes done by setupBusArrangements // vstwrapper ignores side chain channels, pretend they are main inputs uint64 scBusChannels; wrapper->countSidechainBusChannels (kInput, scBusChannels); wrapper->mMainAudioInputBuses |= scBusChannels; if (auto factory2 = U::cast (factory)) { PFactoryInfo factoryInfo; if (factory2->getFactoryInfo (&factoryInfo) == kResultTrue) wrapper->setVendorName (factoryInfo.vendor); for (int32 i = 0; i < factory2->countClasses (); i++) { Steinberg::PClassInfo2 classInfo2; if (factory2->getClassInfo2 (i, &classInfo2) == Steinberg::kResultTrue) { if (memcmp (classInfo2.cid, vst3ComponentID, sizeof (TUID)) == 0) { wrapper->setSubCategories (classInfo2.subCategories); wrapper->setEffectName (classInfo2.name); if (classInfo2.vendor[0] != 0) wrapper->setVendorName (classInfo2.vendor); break; } } } } return wrapper; } //------------------------------------------------------------------------------- int32 AAXWrapper::countSidechainBusChannels (BusDirection dir, uint64& scBusBitset) { int32 result = 0; scBusBitset = 0; int32 busCount = mComponent->getBusCount (kAudio, dir); for (int32 i = 0; i < busCount; i++) { BusInfo busInfo = {}; if (mComponent->getBusInfo (kAudio, dir, i, busInfo) == kResultTrue) { if (busInfo.busType == kAux) { result += busInfo.channelCount; scBusBitset |= (uint64 (1) << i); // no longer activate side chains by default, use the host // notifications instead // mComponent->activateBus (kAudio, dir, i, true); } } } return result; } //------------------------------------------------------------------------ tresult AAXWrapper::setupBusArrangements (AAX_Plugin_Desc* desc) { uint32 inputBusCount = (desc->mInputChannels > 0 ? 1u : 0u) + (desc->mSideChainInputChannels > 0 ? 1u : 0u); uint32 outputBusCount = (desc->mOutputChannels > 0 ? 1u : 0u); if (AAX_Aux_Desc* aux = desc->mAuxOutputChannels) while ((aux++)->mName) outputBusCount++; std::vector inputs (inputBusCount); std::vector outputs (outputBusCount); uint32 in = 0; if (desc->mInputChannels) inputs[in++] = numChannelsToSpeakerArrangement (desc->mInputChannels); if (desc->mSideChainInputChannels) inputs[in++] = numChannelsToSpeakerArrangement (desc->mSideChainInputChannels); if (desc->mOutputChannels) outputs[0] = numChannelsToSpeakerArrangement (desc->mOutputChannels); if (AAX_Aux_Desc* aux = desc->mAuxOutputChannels) for (uint32 i = 0; aux[i].mName; i++) outputs[i + 1u] = numChannelsToSpeakerArrangement (aux[i].mChannels); return mProcessor->setBusArrangements (inputs.data (), static_cast (inputBusCount), outputs.data (), static_cast (outputBusCount)); } //------------------------------------------------------------------------ void AAXWrapper::guessActiveOutputs (float** out, uint32 num) { // a channel is considered inactive if the output pointer is to the // same location as one of it's neighboring channels (ProTools seems // to direct all inactive channels to the same output). // FIXME: this heuristic fails for mono outputs std::bitset active; for (uint32 i = 0; i < num; i++) { auto prev = (i > 0 ? out[i - 1] : nullptr); auto next = (i + 1 < num ? out[i + 1] : nullptr); active[i] = (out[i] != prev && out[i] != next); } mActiveChannels = active; } //------------------------------------------------------------------------ void AAXWrapper::updateActiveOutputState () { // some additional copying to avoid missing updates std::bitset channels = mActiveChannels; if (channels == mPropagatedChannels) return; mPropagatedChannels = channels; uint32 channelPos = 0; int32 busCount = mComponent->getBusCount (kAudio, kOutput); for (int32 i = 0; i < busCount; i++) { BusInfo busInfo = {}; if (mComponent->getBusInfo (kAudio, kOutput, i, busInfo) == kResultTrue) { bool active = false; for (uint32 c = 0; c < static_cast (busInfo.channelCount); c++) if (channels[channelPos + c]) active = true; channelPos += busInfo.channelCount; mComponent->activateBus (kAudio, kOutput, i, active); } } } //------------------------------------------------------------------------ void AAXWrapper::setSideChainEnable (bool enable) { int32 busCount = mComponent->getBusCount (kAudio, kInput); for (int32 i = 0; i < busCount; i++) { BusInfo busInfo = {}; if (mComponent->getBusInfo (kAudio, kInput, i, busInfo) == kResultTrue) { if (busInfo.busType == kAux) { mComponent->activateBus (kAudio, kInput, i, enable); break; } } } } //----------------------------------------------------------------------------- void AAXWrapper::setRenderingOffline (bool val) { if (val) { if (mVst3processMode == kOffline) return; mVst3processMode = kOffline; } else { if (mVst3processMode == kRealtime) return; mVst3processMode = kRealtime; } bool callStartStop = mProcessing; if (callStartStop) BaseWrapper::_stopProcess (); setupProcessing (); if (callStartStop) BaseWrapper::_startProcess (); } //------------------------------------------------------------------------ // Context structure //------------------------------------------------------------------------ //------------------------------------------------------------------------ using fnCreateParameters = AAX_IEffectParameters* AAX_CALLBACK (); template struct CP { static AAX_IEffectParameters* AAX_CALLBACK Create_Parameters () { auto p = NEW AAXWrapper_Parameters (pluginIndex); if (!p->getWrapper ()) { delete p; p = nullptr; } return p; } }; //------------------------------------------------------------------------ AAX_IEffectGUI* AAX_CALLBACK Create_GUI () { return NEW AAXWrapper_GUI (); } //------------------------------------------------------------------------ int32_t AAX_CALLBACK AlgorithmInitFunction (const AAXWrapper_Context* /*inInstance*/, AAX_EComponentInstanceInitAction /*inAction*/) { return AAX_SUCCESS; } //------------------------------------------------------------------------ Steinberg::int32 AAXWrapper::ResetFieldData (Steinberg::int32 index, void* inData, Steinberg::uint32 inDataSize) { if (index == idxContext && inDataSize == sizeof (AAXWrapper*)) { _suspend (); _resume (); *(AAXWrapper**)inData = this; } else // Default implementation is just to zero out all data. memset (inData, 0, inDataSize); return AAX_SUCCESS; } //------------------------------------------------------------------------ Steinberg::int32 AAXWrapper::Process (AAXWrapper_Context* instance) { //--- ------ Retrieve instance-specific information --------- // Memory blocks const int32_t bufferSize = *static_cast (instance->ptr[idxBufferSize]); AAX_ASSERT (bufferSize <= 1024); uint32 cntMidiPorts = getNumMIDIports (); for (uint32 m = 0; m < cntMidiPorts; m++) { auto* midiNode = static_cast (instance->ptr[idxMidiPorts + m]); AAX_CMidiStream* midiBuffer = midiNode->GetNodeBuffer (); //- Check incoming MIDI packets () // for (uint32_t i = 0; i < midiBuffer->mBufferSize; i++) { AAX_CMidiPacket& buf = midiBuffer->mBuffer[i]; if (buf.mLength > 0) { // skip note-on events if bypassed to reduce processor load for instruments, // but let everything else through to avoid hanging notes if (mSimulateBypass && mBypass) if ((buf.mData[0] & Vst::kStatusMask) == Vst::kNoteOn && buf.mData[2] != 0) continue; Event toAdd = {static_cast (m), static_cast (buf.mTimestamp), 0}; bool isLive = buf.mIsImmediate || buf.mTimestamp == 0; processMidiEvent (toAdd, (char*)&buf.mData[0], isLive); } } } auto pdI = idxInputChannels < 0 ? nullptr : static_cast (instance->ptr[idxInputChannels]); // First input float* inputs[16] = {nullptr}; if (pdI && idxSideChainInputChannels >= 0) { if (auto psc = instance->ptr[idxSideChainInputChannels]) { int32 scChannel = *static_cast (psc); int32 idx = mPluginDesc->mInputChannels; memcpy (inputs, pdI, idx * sizeof (inputs[0])); for (int32 i = 0; i < mPluginDesc->mSideChainInputChannels; i++) inputs[idx + i] = pdI[scChannel]; pdI = inputs; } } // First output float** const AAX_RESTRICT pdO = static_cast (instance->ptr[idxOutputChannels]); if (pdO == nullptr) return AAX_ERROR_NULL_ARGUMENT; uint32 cntOut = getNumOutputs (); uint32 aaxOut = getNumAAXOutputs (); float* outputs[maxActiveChannels]; uint32 mainOuts = static_cast (mPluginDesc->mOutputChannels); if (mainOuts == 6) { // sort Surround channels from AAX (L C R Ls Rs LFE) to VST (L R C LFE Ls Rs) outputs[0] = pdO[0]; outputs[1] = pdO[2]; outputs[2] = pdO[1]; outputs[3] = pdO[5]; outputs[4] = pdO[3]; outputs[5] = pdO[4]; } else mainOuts = 0; for (uint32 i = mainOuts; i < aaxOut; i++) outputs[i] = pdO[i]; float buf[1024]; for (uint32 i = aaxOut; i < cntOut; i++) outputs[i] = buf; guessActiveOutputs (outputs, cntOut); mMetersTmp = (mCntMeters > 0) ? *static_cast (instance->ptr[idxMeters]) : nullptr; _processReplacing (pdI, outputs, bufferSize); mMetersTmp = nullptr; // apply bypass if not supported (only without inputs for now, i.e. instruments) // ---------------------- if (mSimulateBypass && mPluginDesc->mInputChannels == 0) { static const float kDiffGain = 0.001f; if (mBypass) { int32 bufPos = 0; while (mBypassGain > 0 && bufPos < bufferSize) { for (uint32 i = 0; i < cntOut; i++) outputs[i][bufPos] *= mBypassGain; mBypassGain -= kDiffGain; bufPos++; } for (uint32 i = 0; i < cntOut; i++) memset (&outputs[i][bufPos], 0, (bufferSize - bufPos) * sizeof (float)); } else if (mBypassGain < 1) { int32 bufPos = 0; while (mBypassGain < 1 && bufPos < bufferSize) { for (uint32 i = 0; i < cntOut; i++) outputs[i][bufPos] *= mBypassGain; mBypassGain += kDiffGain; bufPos++; } } } return AAX_SUCCESS; } //------------------------------------------------------------------------ void AAXWrapper::processOutputParametersChanges () { if (!mMetersTmp) return; uint32 found = 0; // VU Meter readout for (int32 i = 0, count = mOutputChanges.getParameterCount (); i < count; i++) { auto queue = mOutputChanges.getParameterData (i); if (!queue) break; for (uint32 m = 0; m < mCntMeters; m++) { if (mMeterIds[m] == queue->getParameterId ()) { int32 sampleOffset; ParamValue value; queue->getPoint (queue->getPointCount () - 1, sampleOffset, value); mMetersTmp[m] = static_cast (value); found++; break; } } if (found == mCntMeters) break; } } //----------------------------------------------------------------------------- Steinberg::tresult PLUGIN_API AAXWrapper::restartComponent (Steinberg::int32 flags) { tresult result = BaseWrapper::restartComponent (flags); //--- ---------------------- if (flags & kLatencyChanged) { if (mAAXParams && mProcessor) { AAX_IController* ctrler = mAAXParams->Controller (); if (ctrler) ctrler->SetSignalLatency (static_cast (mProcessor->getLatencySamples ())); } result = kResultTrue; } return result; } //------------------------------------------------------------------------ static void AAX_CALLBACK AlgorithmProcessFunction (AAXWrapper_Context* const inInstancesBegin[], const void* inInstancesEnd) { //--- ------ Iterate over plug-in instances ---------// for (AAXWrapper_Context* const* walk = inInstancesBegin; walk < inInstancesEnd; ++walk) { AAXWrapper_Context* AAX_RESTRICT instance = *walk; // first element is Context AAXWrapper* wrapper = *reinterpret_cast (instance->ptr[0]); if (!wrapper) continue; wrapper->Process (instance); } // End instance-iteration loop } //------------------------------------------------------------------------ static uint32 vst3Category2AAXPlugInCategory (const char* cat) { static const uint32 PDA_ePlugInCategory_Effect = AAX_ePlugInCategory_None; // does not exist anymore? uint32 result = AAX_ePlugInCategory_None; if (strstr (cat, "Fx") != nullptr) { result = PDA_ePlugInCategory_Effect; } if (strstr (cat, "Instrument") != nullptr || strstr (cat, "Generator") != nullptr) { if (strstr (cat, "External") != nullptr) result |= AAX_ePlugInCategory_HWGenerators; else result |= AAX_ePlugInCategory_SWGenerators; } if (strstr (cat, "Delay") != nullptr) result |= AAX_ePlugInCategory_Delay; if (strstr (cat, "Distortion") != nullptr) result |= AAX_ePlugInCategory_Harmonic; if (strstr (cat, "Dynamics") != nullptr) result |= AAX_ePlugInCategory_Dynamics; if (strstr (cat, "EQ") != nullptr) result |= AAX_ePlugInCategory_EQ; if (strstr (cat, "Mastering") != nullptr) result |= AAX_ePlugInCategory_Dither; if (strstr (cat, "Modulation") != nullptr) result |= AAX_ePlugInCategory_Modulation; if (strstr (cat, "Pitch Shift") != nullptr) result |= AAX_ePlugInCategory_PitchShift; if (strstr (cat, "Restoration") != nullptr) result |= AAX_ePlugInCategory_NoiseReduction; if (strstr (cat, "Reverb") != nullptr) result |= AAX_ePlugInCategory_Reverb; if (strstr (cat, "Spatial") != nullptr || strstr (cat, "Surround") != nullptr || strstr (cat, "Up-Downmix") != nullptr) { result |= AAX_ePlugInCategory_SoundField; } return result; } //------------------------------------------------------------------------ // ROUTINE: DescribeAlgorithmComponent // Algorithm component description //------------------------------------------------------------------------ void AAXWrapper::DescribeAlgorithmComponent (AAX_IComponentDescriptor* outDesc, const AAX_Effect_Desc* desc, const AAX_Plugin_Desc* pdesc) { // Describe algorithm's context structure // // Subscribe context fields to host-provided services or information HLOG (HAPI, "%s", __FUNCTION__); AAX_Result err = AAX_SUCCESS; // must be in lock step with AAXWrapper ctor int32 idx = idxBufferSize + 1; // ProTools does not like instruments without inputs (maybe because they are treated as // inserts?) int32 inChannels = pdesc->mInputChannels; if (inChannels == 0) inChannels = pdesc->mOutputChannels; if (inChannels) err = outDesc->AddAudioIn (idx++); AAX_ASSERT (err == AAX_SUCCESS); if (pdesc->mOutputChannels) err = outDesc->AddAudioOut (idx++); AAX_ASSERT (err == AAX_SUCCESS); err = outDesc->AddAudioBufferLength (AAXWrapper::idxBufferSize); AAX_ASSERT (err == AAX_SUCCESS); if (pdesc->mSideChainInputChannels) err = outDesc->AddSideChainIn (idx++); // max 1 side chain!? AAX_ASSERT (err == AAX_SUCCESS); if (pdesc->mMIDIports) { for (AAX_MIDI_Desc* mdesc = pdesc->mMIDIports; mdesc->mName; mdesc++) { err = outDesc->AddMIDINode (idx++, AAX_eMIDINodeType_LocalInput, mdesc->mName, mdesc->mMask); AAX_ASSERT (err == 0); } } if (pdesc->mAuxOutputChannels) { for (AAX_Aux_Desc* auxdesc = pdesc->mAuxOutputChannels; auxdesc->mName; auxdesc++) { int32 ch = (auxdesc->mChannels < 0 ? pdesc->mOutputChannels : auxdesc->mChannels); err = outDesc->AddAuxOutputStem (idx++, getChannelsStem (ch), auxdesc->mName); AAX_ASSERT (err == 0); } } if (pdesc->mMeters) { uint32 cntMeters = 0; for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++) cntMeters++; std::unique_ptr meterIds; meterIds.reset (NEW Steinberg::Vst::ParamID (cntMeters)); cntMeters = 0; for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++) meterIds[cntMeters++] = mdesc->mID; err = outDesc->AddMeters (idx++, (AAX_CTypeID*)meterIds.get (), cntMeters); AAX_ASSERT (err == AAX_SUCCESS); } // Register context fields as private data err = outDesc->AddPrivateData (AAXWrapper::idxContext, sizeof (void*), AAX_ePrivateDataOptions_DefaultOptions); AAX_ASSERT (err == AAX_SUCCESS); // Register processing callbacks // // Create a property map AAX_IPropertyMap* properties = outDesc->NewPropertyMap (); AAX_ASSERT (properties); if (!properties) return; // // Generic properties properties->AddProperty (AAX_eProperty_ManufacturerID, static_cast (desc->mManufacturerID)); properties->AddProperty (AAX_eProperty_ProductID, static_cast (desc->mProductID)); properties->AddProperty (AAX_eProperty_CanBypass, true); properties->AddProperty (AAX_eProperty_LatencyContribution, static_cast (pdesc->mLatency)); // properties->AddProperty (AAX_eProperty_UsesClientGUI, true); // Uses auto-GUI // // Stem format -specific properties // AAX_IPropertyMap* propertiesMono = outDesc->NewPropertyMap(); // AAX_ASSERT (propertiesMono); // if ( !propertiesMono ) return; // properties->Clone(propertiesMono); // if (pdesc->mInputChannels) properties->AddProperty (AAX_eProperty_InputStemFormat, getChannelsStem (pdesc->mInputChannels)); else if (pdesc->mOutputChannels) properties->AddProperty (AAX_eProperty_InputStemFormat, getChannelsStem (pdesc->mOutputChannels)); if (pdesc->mOutputChannels) properties->AddProperty (AAX_eProperty_OutputStemFormat, getChannelsStem (pdesc->mOutputChannels)); if (pdesc->mSideChainInputChannels) { properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); #if 0 // only mono supported, setting stem format causes plugin load failure properties->AddProperty (AAX_eProperty_SideChainStemFormat, getChannelsStem (pdesc->mSideChainInputChannels)); #endif } properties->AddProperty (AAX_eProperty_PlugInID_Native, static_cast (pdesc->mPlugInIDNative)); properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, static_cast (pdesc->mPlugInIDAudioSuite)); // Register callbacks // Native (Native and AudioSuite) err = outDesc->AddProcessProc_Native ( AlgorithmProcessFunction, properties, AlgorithmInitFunction, nullptr /*AlgorithmBackgroundFunction*/); AAX_ASSERT (err == AAX_SUCCESS); } //------------------------------------------------------------------------ static AAX_Result GetPlugInDescription (AAX_IEffectDescriptor* outDescriptor, const AAX_Effect_Desc* desc, const AAX_Plugin_Desc* pdesc) { HLOG (HAPI, "%s", __FUNCTION__); int err = AAX_SUCCESS; AAX_IComponentDescriptor* compDesc = outDescriptor->NewComponentDescriptor (); if (!compDesc) return AAX_ERROR_NULL_OBJECT; // Effect identifiers outDescriptor->AddName (pdesc->mName); outDescriptor->AddCategory (vst3Category2AAXPlugInCategory (desc->mCategory)); // Effect components // // Algorithm component AAXWrapper::DescribeAlgorithmComponent (compDesc, desc, pdesc); err = outDescriptor->AddComponent (compDesc); AAX_ASSERT (err == AAX_SUCCESS); // Data model int32 plugIndex = static_cast (pdesc - desc->mPluginDesc); fnCreateParameters* fn = nullptr; switch (plugIndex) { #define ADDPROCPTR(N) \ case N: fn = &CP::Create_Parameters; break; ADDPROCPTR (0) ADDPROCPTR (1) ADDPROCPTR (2) ADDPROCPTR (3) ADDPROCPTR (4) ADDPROCPTR (5) ADDPROCPTR (6) ADDPROCPTR (7) ADDPROCPTR (8) ADDPROCPTR (9) ADDPROCPTR (10) ADDPROCPTR (11) ADDPROCPTR (12) ADDPROCPTR (13) ADDPROCPTR (14) ADDPROCPTR (15) } AAX_ASSERT (fn); err = outDescriptor->AddProcPtr ((void*)fn, kAAX_ProcPtrID_Create_EffectParameters); AAX_ASSERT (err == AAX_SUCCESS); if (desc->mPageFile) outDescriptor->AddResourceInfo (AAX_eResourceType_PageTable, desc->mPageFile); // Effect's meter display properties if (pdesc->mMeters) { for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++) { AAX_IPropertyMap* meterProperties = outDescriptor->NewPropertyMap (); if (!meterProperties) return AAX_ERROR_NULL_OBJECT; // Support different meter types offered by AAX here meterProperties->AddProperty (AAX_eProperty_Meter_Type, static_cast (mdesc->mType)); meterProperties->AddProperty (AAX_eProperty_Meter_Orientation, static_cast (mdesc->mOrientation)); outDescriptor->AddMeterDescription (mdesc->mID, mdesc->mName, meterProperties); } } // plugin supplied GUI err = outDescriptor->AddProcPtr ((void*)Create_GUI, kAAX_ProcPtrID_Create_EffectGUI); AAX_ASSERT (err == AAX_SUCCESS); return AAX_SUCCESS; } //------------------------------------------------------------------------ AAX_Result GetEffectDescriptions (AAX_ICollection* outCollection) { HLOG (HAPI, "%s", __FUNCTION__); AAX_Result result = AAX_ERROR_NULL_OBJECT; AAX_Effect_Desc* effDesc = AAXWrapper_GetDescription (); for (const AAX_Plugin_Desc* pdesc = effDesc->mPluginDesc; pdesc->mEffectID; pdesc++) { if (AAX_IEffectDescriptor* plugInDescriptor = outCollection->NewDescriptor ()) { result = GetPlugInDescription (plugInDescriptor, effDesc, pdesc); if (result == AAX_SUCCESS) result = outCollection->AddEffect (pdesc->mEffectID, plugInDescriptor); AAX_ASSERT (result == AAX_SUCCESS); } } outCollection->SetManufacturerName (effDesc->mManufacturer); /* needed ? char packageName[512]; sprintf (packageName, "%s AAX Plug-In", effDesc->mProduct); outCollection->AddPackageName (packageName); sprintf (packageName, "%s Plug-In", effDesc->mProduct); outCollection->AddPackageName (packageName);*/ outCollection->AddPackageName (effDesc->mProduct); if (strlen (effDesc->mProduct) > 16) { char packageShortName[17] = {0}; strncpy (packageShortName, effDesc->mProduct, 16); outCollection->AddPackageName (packageShortName); } outCollection->SetPackageVersion (effDesc->mVersion); return result; } /// \endcond