2293 lines
87 KiB
C++
2293 lines
87 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2017 - ROLI Ltd.
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||
|
27th April 2017).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-5-licence
|
||
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
#include "../../juce_core/system/juce_TargetPlatform.h"
|
||
|
#include "../utility/juce_CheckSettingMacros.h"
|
||
|
|
||
|
#if JucePlugin_Build_VST
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma warning (disable : 4996 4100)
|
||
|
#endif
|
||
|
|
||
|
#include "../utility/juce_IncludeSystemHeaders.h"
|
||
|
|
||
|
#ifdef PRAGMA_ALIGN_SUPPORTED
|
||
|
#undef PRAGMA_ALIGN_SUPPORTED
|
||
|
#define PRAGMA_ALIGN_SUPPORTED 1
|
||
|
#endif
|
||
|
|
||
|
#ifndef _MSC_VER
|
||
|
#define __cdecl
|
||
|
#endif
|
||
|
|
||
|
#if JUCE_CLANG
|
||
|
#pragma clang diagnostic push
|
||
|
#pragma clang diagnostic ignored "-Wconversion"
|
||
|
#pragma clang diagnostic ignored "-Wshadow"
|
||
|
#pragma clang diagnostic ignored "-Wdeprecated-register"
|
||
|
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||
|
#pragma clang diagnostic ignored "-Wdeprecated-writable-strings"
|
||
|
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
|
||
|
#endif
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma warning (push)
|
||
|
#pragma warning (disable : 4458)
|
||
|
#endif
|
||
|
|
||
|
#include <juce_core/juce_core.h>
|
||
|
#include "../../juce_audio_processors/format_types/juce_VSTInterface.h"
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma warning (pop)
|
||
|
#endif
|
||
|
|
||
|
#if JUCE_CLANG
|
||
|
#pragma clang diagnostic pop
|
||
|
#endif
|
||
|
|
||
|
//==============================================================================
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma pack (push, 8)
|
||
|
#endif
|
||
|
|
||
|
#include "../utility/juce_IncludeModuleHeaders.h"
|
||
|
#include "../utility/juce_FakeMouseMoveGenerator.h"
|
||
|
#include "../utility/juce_WindowsHooks.h"
|
||
|
|
||
|
#include "../../juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp"
|
||
|
#include "../../juce_audio_processors/format_types/juce_VSTCommon.h"
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma pack (pop)
|
||
|
#endif
|
||
|
|
||
|
#undef MemoryBlock
|
||
|
|
||
|
class JuceVSTWrapper;
|
||
|
static bool recursionCheck = false;
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
#if JUCE_MAC
|
||
|
extern JUCE_API void initialiseMacVST();
|
||
|
extern JUCE_API void* attachComponentToWindowRefVST (Component*, void* parent, bool isNSView);
|
||
|
extern JUCE_API void detachComponentFromWindowRefVST (Component*, void* window, bool isNSView);
|
||
|
extern JUCE_API void setNativeHostWindowSizeVST (void* window, Component*, int newWidth, int newHeight, bool isNSView);
|
||
|
extern JUCE_API void checkWindowVisibilityVST (void* window, Component*, bool isNSView);
|
||
|
extern JUCE_API bool forwardCurrentKeyEventToHostVST (Component*, bool isNSView);
|
||
|
#if ! JUCE_64BIT
|
||
|
extern JUCE_API void updateEditorCompBoundsVST (Component*);
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
extern JUCE_API bool handleManufacturerSpecificVST2Opcode (int32, pointer_sized_int, void*, float);
|
||
|
}
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
#if JUCE_WINDOWS
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
// Returns the actual container window, unlike GetParent, which can also return a separate owner window.
|
||
|
static HWND getWindowParent (HWND w) noexcept { return GetAncestor (w, GA_PARENT); }
|
||
|
|
||
|
static HWND findMDIParentOf (HWND w)
|
||
|
{
|
||
|
const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME);
|
||
|
|
||
|
while (w != 0)
|
||
|
{
|
||
|
auto parent = getWindowParent (w);
|
||
|
|
||
|
if (parent == 0)
|
||
|
break;
|
||
|
|
||
|
TCHAR windowType[32] = { 0 };
|
||
|
GetClassName (parent, windowType, 31);
|
||
|
|
||
|
if (String (windowType).equalsIgnoreCase ("MDIClient"))
|
||
|
return parent;
|
||
|
|
||
|
RECT windowPos, parentPos;
|
||
|
GetWindowRect (w, &windowPos);
|
||
|
GetWindowRect (parent, &parentPos);
|
||
|
|
||
|
auto dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left);
|
||
|
auto dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top);
|
||
|
|
||
|
if (dw > 100 || dh > 100)
|
||
|
break;
|
||
|
|
||
|
w = parent;
|
||
|
|
||
|
if (dw == 2 * frameThickness)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return w;
|
||
|
}
|
||
|
|
||
|
static bool messageThreadIsDefinitelyCorrect = false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
#elif JUCE_LINUX
|
||
|
|
||
|
struct SharedMessageThread : public Thread
|
||
|
{
|
||
|
SharedMessageThread() : Thread ("VstMessageThread")
|
||
|
{
|
||
|
startThread (7);
|
||
|
|
||
|
while (! initialised)
|
||
|
sleep (1);
|
||
|
}
|
||
|
|
||
|
~SharedMessageThread()
|
||
|
{
|
||
|
signalThreadShouldExit();
|
||
|
JUCEApplicationBase::quit();
|
||
|
waitForThreadToExit (5000);
|
||
|
clearSingletonInstance();
|
||
|
}
|
||
|
|
||
|
void run() override
|
||
|
{
|
||
|
initialiseJuce_GUI();
|
||
|
initialised = true;
|
||
|
|
||
|
MessageManager::getInstance()->setCurrentThreadAsMessageThread();
|
||
|
|
||
|
ScopedXDisplay xDisplay;
|
||
|
|
||
|
while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250))
|
||
|
{}
|
||
|
}
|
||
|
|
||
|
JUCE_DECLARE_SINGLETON (SharedMessageThread, false)
|
||
|
|
||
|
bool initialised = false;
|
||
|
};
|
||
|
|
||
|
JUCE_IMPLEMENT_SINGLETON (SharedMessageThread)
|
||
|
|
||
|
#endif
|
||
|
|
||
|
static Array<void*> activePlugins;
|
||
|
|
||
|
//==============================================================================
|
||
|
// Ableton Live host specific commands
|
||
|
struct AbletonLiveHostSpecific
|
||
|
{
|
||
|
enum
|
||
|
{
|
||
|
KCantBeSuspended = (1 << 2)
|
||
|
};
|
||
|
|
||
|
uint32 magic; // 'AbLi'
|
||
|
int cmd; // 5 = realtime properties
|
||
|
size_t commandSize; // sizeof (int)
|
||
|
int flags; // KCantBeSuspended = (1 << 2)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
/**
|
||
|
This is an AudioEffectX object that holds and wraps our AudioProcessor...
|
||
|
*/
|
||
|
class JuceVSTWrapper : public AudioProcessorListener,
|
||
|
public AudioPlayHead,
|
||
|
private Timer,
|
||
|
private AsyncUpdater,
|
||
|
private AudioProcessorParameter::Listener
|
||
|
{
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
template <typename FloatType>
|
||
|
struct VstTempBuffers
|
||
|
{
|
||
|
VstTempBuffers() {}
|
||
|
~VstTempBuffers() { release(); }
|
||
|
|
||
|
void release() noexcept
|
||
|
{
|
||
|
for (auto* c : tempChannels)
|
||
|
delete[] c;
|
||
|
|
||
|
tempChannels.clear();
|
||
|
}
|
||
|
|
||
|
HeapBlock<FloatType*> channels;
|
||
|
Array<FloatType*> tempChannels; // see note in processReplacing()
|
||
|
juce::AudioBuffer<FloatType> processTempBuffer;
|
||
|
};
|
||
|
|
||
|
/** Use the same names as the VST SDK. */
|
||
|
struct VstOpCodeArguments
|
||
|
{
|
||
|
int32 index;
|
||
|
pointer_sized_int value;
|
||
|
void* ptr;
|
||
|
float opt;
|
||
|
};
|
||
|
|
||
|
public:
|
||
|
//==============================================================================
|
||
|
JuceVSTWrapper (VstHostCallback cb, AudioProcessor* af)
|
||
|
: hostCallback (cb),
|
||
|
processor (af)
|
||
|
{
|
||
|
inParameterChangedCallback = false;
|
||
|
|
||
|
// VST-2 does not support disabling buses: so always enable all of them
|
||
|
processor->enableAllBuses();
|
||
|
|
||
|
findMaxTotalChannels (maxNumInChannels, maxNumOutChannels);
|
||
|
|
||
|
// You must at least have some channels
|
||
|
jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0));
|
||
|
|
||
|
if (processor->isMidiEffect())
|
||
|
maxNumInChannels = maxNumOutChannels = 2;
|
||
|
|
||
|
#ifdef JucePlugin_PreferredChannelConfigurations
|
||
|
processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024);
|
||
|
#endif
|
||
|
|
||
|
processor->setRateAndBufferSizeDetails (0, 0);
|
||
|
processor->setPlayHead (this);
|
||
|
processor->addListener (this);
|
||
|
|
||
|
if (auto* juceParam = processor->getBypassParameter())
|
||
|
juceParam->addListener (this);
|
||
|
|
||
|
juceParameters.update (*processor, false);
|
||
|
|
||
|
memset (&vstEffect, 0, sizeof (vstEffect));
|
||
|
vstEffect.interfaceIdentifier = juceVstInterfaceIdentifier;
|
||
|
vstEffect.dispatchFunction = dispatcherCB;
|
||
|
vstEffect.processAudioFunction = nullptr;
|
||
|
vstEffect.setParameterValueFunction = setParameterCB;
|
||
|
vstEffect.getParameterValueFunction = getParameterCB;
|
||
|
vstEffect.numPrograms = jmax (1, af->getNumPrograms());
|
||
|
vstEffect.numParameters = juceParameters.getNumParameters();
|
||
|
vstEffect.numInputChannels = maxNumInChannels;
|
||
|
vstEffect.numOutputChannels = maxNumOutChannels;
|
||
|
vstEffect.latency = processor->getLatencySamples();
|
||
|
vstEffect.effectPointer = this;
|
||
|
vstEffect.plugInIdentifier = JucePlugin_VSTUniqueID;
|
||
|
|
||
|
#ifdef JucePlugin_VSTChunkStructureVersion
|
||
|
vstEffect.plugInVersion = JucePlugin_VSTChunkStructureVersion;
|
||
|
#else
|
||
|
vstEffect.plugInVersion = JucePlugin_VersionCode;
|
||
|
#endif
|
||
|
|
||
|
vstEffect.processAudioInplaceFunction = processReplacingCB;
|
||
|
vstEffect.processDoubleAudioInplaceFunction = processDoubleReplacingCB;
|
||
|
|
||
|
vstEffect.flags |= vstEffectFlagHasEditor;
|
||
|
|
||
|
vstEffect.flags |= vstEffectFlagInplaceAudio;
|
||
|
if (processor->supportsDoublePrecisionProcessing())
|
||
|
vstEffect.flags |= vstEffectFlagInplaceDoubleAudio;
|
||
|
|
||
|
#if JucePlugin_IsSynth
|
||
|
vstEffect.flags |= vstEffectFlagIsSynth;
|
||
|
#endif
|
||
|
|
||
|
vstEffect.flags |= vstEffectFlagDataInChunks;
|
||
|
|
||
|
activePlugins.add (this);
|
||
|
}
|
||
|
|
||
|
~JuceVSTWrapper()
|
||
|
{
|
||
|
JUCE_AUTORELEASEPOOL
|
||
|
{
|
||
|
{
|
||
|
#if JUCE_LINUX
|
||
|
MessageManagerLock mmLock;
|
||
|
#endif
|
||
|
stopTimer();
|
||
|
deleteEditor (false);
|
||
|
|
||
|
hasShutdown = true;
|
||
|
|
||
|
delete processor;
|
||
|
processor = nullptr;
|
||
|
|
||
|
jassert (editorComp == nullptr);
|
||
|
|
||
|
deleteTempChannels();
|
||
|
|
||
|
jassert (activePlugins.contains (this));
|
||
|
activePlugins.removeFirstMatchingValue (this);
|
||
|
}
|
||
|
|
||
|
if (activePlugins.size() == 0)
|
||
|
{
|
||
|
#if JUCE_LINUX
|
||
|
SharedMessageThread::deleteInstance();
|
||
|
#endif
|
||
|
shutdownJuce_GUI();
|
||
|
|
||
|
#if JUCE_WINDOWS
|
||
|
messageThreadIsDefinitelyCorrect = false;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VstEffectInterface* getVstEffectInterface() noexcept { return &vstEffect; }
|
||
|
|
||
|
template <typename FloatType>
|
||
|
void internalProcessReplacing (FloatType** inputs, FloatType** outputs,
|
||
|
int32 numSamples, VstTempBuffers<FloatType>& tmpBuffers)
|
||
|
{
|
||
|
const bool isMidiEffect = processor->isMidiEffect();
|
||
|
|
||
|
if (firstProcessCallback)
|
||
|
{
|
||
|
firstProcessCallback = false;
|
||
|
|
||
|
// if this fails, the host hasn't called resume() before processing
|
||
|
jassert (isProcessing);
|
||
|
|
||
|
// (tragically, some hosts actually need this, although it's stupid to have
|
||
|
// to do it here..)
|
||
|
if (! isProcessing)
|
||
|
resume();
|
||
|
|
||
|
processor->setNonRealtime (isProcessLevelOffline());
|
||
|
|
||
|
#if JUCE_WINDOWS
|
||
|
if (getHostType().isWavelab())
|
||
|
{
|
||
|
int priority = GetThreadPriority (GetCurrentThread());
|
||
|
|
||
|
if (priority <= THREAD_PRIORITY_NORMAL && priority >= THREAD_PRIORITY_LOWEST)
|
||
|
processor->setNonRealtime (true);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if JUCE_DEBUG && ! (JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect)
|
||
|
const int numMidiEventsComingIn = midiEvents.getNumEvents();
|
||
|
#endif
|
||
|
|
||
|
jassert (activePlugins.contains (this));
|
||
|
|
||
|
{
|
||
|
const int numIn = processor->getTotalNumInputChannels();
|
||
|
const int numOut = processor->getTotalNumOutputChannels();
|
||
|
|
||
|
const ScopedLock sl (processor->getCallbackLock());
|
||
|
|
||
|
if (processor->isSuspended())
|
||
|
{
|
||
|
for (int i = 0; i < numOut; ++i)
|
||
|
if (outputs[i] != nullptr)
|
||
|
FloatVectorOperations::clear (outputs[i], numSamples);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < numOut; ++i)
|
||
|
{
|
||
|
auto* chan = tmpBuffers.tempChannels.getUnchecked(i);
|
||
|
|
||
|
if (chan == nullptr)
|
||
|
{
|
||
|
chan = outputs[i];
|
||
|
|
||
|
bool bufferPointerReusedForOtherChannels = false;
|
||
|
|
||
|
for (int j = i; --j >= 0;)
|
||
|
{
|
||
|
if (outputs[j] == chan)
|
||
|
{
|
||
|
bufferPointerReusedForOtherChannels = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if some output channels are disabled, some hosts supply the same buffer
|
||
|
// for multiple channels or supply a nullptr - this buggers up our method
|
||
|
// of copying the inputs over the outputs, so we need to create unique temp
|
||
|
// buffers in this case..
|
||
|
if (bufferPointerReusedForOtherChannels || chan == nullptr)
|
||
|
{
|
||
|
chan = new FloatType [(size_t) blockSize * 2];
|
||
|
tmpBuffers.tempChannels.set (i, chan);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i < numIn)
|
||
|
{
|
||
|
if (chan != inputs[i])
|
||
|
memcpy (chan, inputs[i], sizeof (FloatType) * (size_t) numSamples);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FloatVectorOperations::clear (chan, numSamples);
|
||
|
}
|
||
|
|
||
|
tmpBuffers.channels[i] = chan;
|
||
|
}
|
||
|
|
||
|
for (; i < numIn; ++i)
|
||
|
tmpBuffers.channels[i] = inputs[i];
|
||
|
|
||
|
{
|
||
|
const int numChannels = jmax (numIn, numOut);
|
||
|
AudioBuffer<FloatType> chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples);
|
||
|
|
||
|
if (isBypassed)
|
||
|
processor->processBlockBypassed (chans, midiEvents);
|
||
|
else
|
||
|
processor->processBlock (chans, midiEvents);
|
||
|
}
|
||
|
|
||
|
// copy back any temp channels that may have been used..
|
||
|
for (i = 0; i < numOut; ++i)
|
||
|
if (auto* chan = tmpBuffers.tempChannels.getUnchecked(i))
|
||
|
if (auto* dest = outputs[i])
|
||
|
memcpy (dest, chan, sizeof (FloatType) * (size_t) numSamples);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! midiEvents.isEmpty())
|
||
|
{
|
||
|
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect
|
||
|
auto numEvents = midiEvents.getNumEvents();
|
||
|
|
||
|
outgoingEvents.ensureSize (numEvents);
|
||
|
outgoingEvents.clear();
|
||
|
|
||
|
const uint8* midiEventData;
|
||
|
int midiEventSize, midiEventPosition;
|
||
|
MidiBuffer::Iterator i (midiEvents);
|
||
|
|
||
|
while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition))
|
||
|
{
|
||
|
jassert (midiEventPosition >= 0 && midiEventPosition < numSamples);
|
||
|
|
||
|
outgoingEvents.addEvent (midiEventData, midiEventSize, midiEventPosition);
|
||
|
}
|
||
|
|
||
|
// Send VST events to the host.
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodePreAudioProcessingEvents, 0, 0, outgoingEvents.events, 0);
|
||
|
#elif JUCE_DEBUG
|
||
|
/* This assertion is caused when you've added some events to the
|
||
|
midiMessages array in your processBlock() method, which usually means
|
||
|
that you're trying to send them somewhere. But in this case they're
|
||
|
getting thrown away.
|
||
|
|
||
|
If your plugin does want to send midi messages, you'll need to set
|
||
|
the JucePlugin_ProducesMidiOutput macro to 1 in your
|
||
|
JucePluginCharacteristics.h file.
|
||
|
|
||
|
If you don't want to produce any midi output, then you should clear the
|
||
|
midiMessages array at the end of your processBlock() method, to
|
||
|
indicate that you don't want any of the events to be passed through
|
||
|
to the output.
|
||
|
*/
|
||
|
jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn);
|
||
|
#endif
|
||
|
|
||
|
midiEvents.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void processReplacing (float** inputs, float** outputs, int32 sampleFrames)
|
||
|
{
|
||
|
jassert (! processor->isUsingDoublePrecision());
|
||
|
internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers);
|
||
|
}
|
||
|
|
||
|
static void processReplacingCB (VstEffectInterface* vstInterface, float** inputs, float** outputs, int32 sampleFrames)
|
||
|
{
|
||
|
getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames);
|
||
|
}
|
||
|
|
||
|
void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames)
|
||
|
{
|
||
|
jassert (processor->isUsingDoublePrecision());
|
||
|
internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers);
|
||
|
}
|
||
|
|
||
|
static void processDoubleReplacingCB (VstEffectInterface* vstInterface, double** inputs, double** outputs, int32 sampleFrames)
|
||
|
{
|
||
|
getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void resume()
|
||
|
{
|
||
|
if (processor != nullptr)
|
||
|
{
|
||
|
isProcessing = true;
|
||
|
|
||
|
auto numInAndOutChannels = static_cast<size_t> (vstEffect.numInputChannels + vstEffect.numOutputChannels);
|
||
|
floatTempBuffers .channels.calloc (numInAndOutChannels);
|
||
|
doubleTempBuffers.channels.calloc (numInAndOutChannels);
|
||
|
|
||
|
auto currentRate = sampleRate;
|
||
|
auto currentBlockSize = blockSize;
|
||
|
|
||
|
firstProcessCallback = true;
|
||
|
|
||
|
processor->setNonRealtime (isProcessLevelOffline());
|
||
|
processor->setRateAndBufferSizeDetails (currentRate, currentBlockSize);
|
||
|
|
||
|
deleteTempChannels();
|
||
|
|
||
|
processor->prepareToPlay (currentRate, currentBlockSize);
|
||
|
|
||
|
midiEvents.ensureSize (2048);
|
||
|
midiEvents.clear();
|
||
|
|
||
|
vstEffect.latency = processor->getLatencySamples();
|
||
|
|
||
|
/** If this plug-in is a synth or it can receive midi events we need to tell the
|
||
|
host that we want midi. In the SDK this method is marked as deprecated, but
|
||
|
some hosts rely on this behaviour.
|
||
|
*/
|
||
|
if (vstEffect.flags & vstEffectFlagIsSynth || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect)
|
||
|
{
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodePlugInWantsMidi, 0, 1, 0, 0);
|
||
|
}
|
||
|
|
||
|
if (getHostType().isAbletonLive()
|
||
|
&& hostCallback != nullptr
|
||
|
&& processor->getTailLengthSeconds() == std::numeric_limits<double>::max())
|
||
|
{
|
||
|
AbletonLiveHostSpecific hostCmd;
|
||
|
|
||
|
hostCmd.magic = 0x41624c69; // 'AbLi'
|
||
|
hostCmd.cmd = 5;
|
||
|
hostCmd.commandSize = sizeof (int);
|
||
|
hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended;
|
||
|
|
||
|
hostCallback (&vstEffect, hostOpcodeManufacturerSpecific, 0, 0, &hostCmd, 0.0f);
|
||
|
}
|
||
|
|
||
|
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect
|
||
|
outgoingEvents.ensureSize (512);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void suspend()
|
||
|
{
|
||
|
if (processor != nullptr)
|
||
|
{
|
||
|
processor->releaseResources();
|
||
|
outgoingEvents.freeEvents();
|
||
|
|
||
|
isProcessing = false;
|
||
|
floatTempBuffers.channels.free();
|
||
|
doubleTempBuffers.channels.free();
|
||
|
|
||
|
deleteTempChannels();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override
|
||
|
{
|
||
|
const VstTimingInformation* ti = nullptr;
|
||
|
|
||
|
if (hostCallback != nullptr)
|
||
|
{
|
||
|
int32 flags = vstTimingInfoFlagMusicalPositionValid | vstTimingInfoFlagTempoValid
|
||
|
| vstTimingInfoFlagLastBarPositionValid | vstTimingInfoFlagLoopPositionValid
|
||
|
| vstTimingInfoFlagTimeSignatureValid | vstTimingInfoFlagSmpteValid
|
||
|
| vstTimingInfoFlagNearestClockValid;
|
||
|
|
||
|
auto result = hostCallback (&vstEffect, hostOpcodeGetTimingInfo, 0, flags, 0, 0);
|
||
|
ti = reinterpret_cast<VstTimingInformation*> (result);
|
||
|
}
|
||
|
|
||
|
if (ti == nullptr || ti->sampleRate <= 0)
|
||
|
return false;
|
||
|
|
||
|
info.bpm = (ti->flags & vstTimingInfoFlagTempoValid) != 0 ? ti->tempoBPM : 0.0;
|
||
|
|
||
|
if ((ti->flags & vstTimingInfoFlagTimeSignatureValid) != 0)
|
||
|
{
|
||
|
info.timeSigNumerator = ti->timeSignatureNumerator;
|
||
|
info.timeSigDenominator = ti->timeSignatureDenominator;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
info.timeSigNumerator = 4;
|
||
|
info.timeSigDenominator = 4;
|
||
|
}
|
||
|
|
||
|
info.timeInSamples = (int64) (ti->samplePosition + 0.5);
|
||
|
info.timeInSeconds = ti->samplePosition / ti->sampleRate;
|
||
|
info.ppqPosition = (ti->flags & vstTimingInfoFlagMusicalPositionValid) != 0 ? ti->musicalPosition : 0.0;
|
||
|
info.ppqPositionOfLastBarStart = (ti->flags & vstTimingInfoFlagLastBarPositionValid) != 0 ? ti->lastBarPosition : 0.0;
|
||
|
|
||
|
if ((ti->flags & vstTimingInfoFlagSmpteValid) != 0)
|
||
|
{
|
||
|
AudioPlayHead::FrameRateType rate = AudioPlayHead::fpsUnknown;
|
||
|
double fps = 1.0;
|
||
|
|
||
|
switch (ti->smpteRate)
|
||
|
{
|
||
|
case vstSmpteRateFps239: rate = AudioPlayHead::fps23976; fps = 24.0 * 1000.0 / 1001.0; break;
|
||
|
case vstSmpteRateFps24: rate = AudioPlayHead::fps24; fps = 24.0; break;
|
||
|
case vstSmpteRateFps25: rate = AudioPlayHead::fps25; fps = 25.0; break;
|
||
|
case vstSmpteRateFps2997: rate = AudioPlayHead::fps2997; fps = 30.0 * 1000.0 / 1001.0; break;
|
||
|
case vstSmpteRateFps30: rate = AudioPlayHead::fps30; fps = 30.0; break;
|
||
|
case vstSmpteRateFps2997drop: rate = AudioPlayHead::fps2997drop; fps = 30.0 * 1000.0 / 1001.0; break;
|
||
|
case vstSmpteRateFps30drop: rate = AudioPlayHead::fps30drop; fps = 30.0; break;
|
||
|
|
||
|
case vstSmpteRate16mmFilm:
|
||
|
case vstSmpteRate35mmFilm: fps = 24.0; break;
|
||
|
|
||
|
case vstSmpteRateFps249: fps = 25.0 * 1000.0 / 1001.0; break;
|
||
|
case vstSmpteRateFps599: fps = 60.0 * 1000.0 / 1001.0; break;
|
||
|
case vstSmpteRateFps60: fps = 60; break;
|
||
|
|
||
|
default: jassertfalse; // unknown frame-rate..
|
||
|
}
|
||
|
|
||
|
info.frameRate = rate;
|
||
|
info.editOriginTime = ti->smpteOffset / (80.0 * fps);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
info.frameRate = AudioPlayHead::fpsUnknown;
|
||
|
info.editOriginTime = 0;
|
||
|
}
|
||
|
|
||
|
info.isRecording = (ti->flags & vstTimingInfoFlagCurrentlyRecording) != 0;
|
||
|
info.isPlaying = (ti->flags & (vstTimingInfoFlagCurrentlyRecording | vstTimingInfoFlagCurrentlyPlaying)) != 0;
|
||
|
info.isLooping = (ti->flags & vstTimingInfoFlagLoopActive) != 0;
|
||
|
|
||
|
if ((ti->flags & vstTimingInfoFlagLoopPositionValid) != 0)
|
||
|
{
|
||
|
info.ppqLoopStart = ti->loopStartPosition;
|
||
|
info.ppqLoopEnd = ti->loopEndPosition;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
info.ppqLoopStart = 0;
|
||
|
info.ppqLoopEnd = 0;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
float getParameter (int32 index) const
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (index))
|
||
|
return param->getValue();
|
||
|
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
static float getParameterCB (VstEffectInterface* vstInterface, int32 index)
|
||
|
{
|
||
|
return getWrapper (vstInterface)->getParameter (index);
|
||
|
}
|
||
|
|
||
|
void setParameter (int32 index, float value)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (index))
|
||
|
{
|
||
|
param->setValue (value);
|
||
|
|
||
|
inParameterChangedCallback = true;
|
||
|
param->sendValueChangedMessageToListeners (value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void setParameterCB (VstEffectInterface* vstInterface, int32 index, float value)
|
||
|
{
|
||
|
getWrapper (vstInterface)->setParameter (index, value);
|
||
|
}
|
||
|
|
||
|
void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override
|
||
|
{
|
||
|
if (inParameterChangedCallback.get())
|
||
|
{
|
||
|
inParameterChangedCallback = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodeParameterChanged, index, 0, 0, newValue);
|
||
|
}
|
||
|
|
||
|
void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override
|
||
|
{
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodeParameterChangeGestureBegin, index, 0, 0, 0);
|
||
|
}
|
||
|
|
||
|
void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override
|
||
|
{
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodeParameterChangeGestureEnd, index, 0, 0, 0);
|
||
|
}
|
||
|
|
||
|
void parameterValueChanged (int, float newValue) override
|
||
|
{
|
||
|
// this can only come from the bypass parameter
|
||
|
isBypassed = (newValue != 0.0f);
|
||
|
}
|
||
|
|
||
|
void parameterGestureChanged (int, bool) override {}
|
||
|
|
||
|
void audioProcessorChanged (AudioProcessor*) override
|
||
|
{
|
||
|
vstEffect.latency = processor->getLatencySamples();
|
||
|
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodeUpdateView, 0, 0, 0, 0);
|
||
|
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
void handleAsyncUpdate() override
|
||
|
{
|
||
|
if (hostCallback != nullptr)
|
||
|
hostCallback (&vstEffect, hostOpcodeIOModified, 0, 0, 0, 0);
|
||
|
}
|
||
|
|
||
|
bool getPinProperties (VstPinInfo& properties, bool direction, int index) const
|
||
|
{
|
||
|
if (processor->isMidiEffect())
|
||
|
return false;
|
||
|
|
||
|
int channelIdx, busIdx;
|
||
|
|
||
|
// fill with default
|
||
|
properties.flags = 0;
|
||
|
properties.text[0] = 0;
|
||
|
properties.shortText[0] = 0;
|
||
|
properties.configurationType = vstSpeakerConfigTypeEmpty;
|
||
|
|
||
|
if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0)
|
||
|
{
|
||
|
auto& bus = *processor->getBus (direction, busIdx);
|
||
|
auto& channelSet = bus.getCurrentLayout();
|
||
|
auto channelType = channelSet.getTypeOfChannel (channelIdx);
|
||
|
|
||
|
properties.flags = vstPinInfoFlagIsActive | vstPinInfoFlagValid;
|
||
|
properties.configurationType = SpeakerMappings::channelSetToVstArrangementType (channelSet);
|
||
|
String label = bus.getName();
|
||
|
|
||
|
#ifdef JucePlugin_PreferredChannelConfigurations
|
||
|
label += " " + String (channelIdx);
|
||
|
#else
|
||
|
if (channelSet.size() > 1)
|
||
|
label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType);
|
||
|
#endif
|
||
|
|
||
|
label.copyToUTF8 (properties.text, (size_t) (vstMaxParameterOrPinLabelLength + 1));
|
||
|
label.copyToUTF8 (properties.shortText, (size_t) (vstMaxParameterOrPinShortLabelLength + 1));
|
||
|
|
||
|
if (channelType == AudioChannelSet::left
|
||
|
|| channelType == AudioChannelSet::leftSurround
|
||
|
|| channelType == AudioChannelSet::leftCentre
|
||
|
|| channelType == AudioChannelSet::leftSurroundSide
|
||
|
|| channelType == AudioChannelSet::topFrontLeft
|
||
|
|| channelType == AudioChannelSet::topRearLeft
|
||
|
|| channelType == AudioChannelSet::leftSurroundRear
|
||
|
|| channelType == AudioChannelSet::wideLeft)
|
||
|
properties.flags |= vstPinInfoFlagIsStereo;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace)
|
||
|
{
|
||
|
struct Mapping
|
||
|
{
|
||
|
int32 vst2;
|
||
|
ChannelType channels[13];
|
||
|
|
||
|
bool matches (const Array<ChannelType>& chans) const noexcept
|
||
|
{
|
||
|
const int n = sizeof (channels) / sizeof (ChannelType);
|
||
|
|
||
|
for (int i = 0; i < n; ++i)
|
||
|
{
|
||
|
if (channels[i] == unknown) return (i == chans.size());
|
||
|
if (i == chans.size()) return (channels[i] == unknown);
|
||
|
|
||
|
if (channels[i] != chans.getUnchecked(i))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr)
|
||
|
{
|
||
|
if (arr.type == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled();
|
||
|
if (arr.type == vstSpeakerConfigTypeMono) return AudioChannelSet::mono();
|
||
|
if (arr.type == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS();
|
||
|
if (arr.type == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic();
|
||
|
|
||
|
for (auto* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m)
|
||
|
{
|
||
|
if (m->vst2 == arr.type)
|
||
|
{
|
||
|
AudioChannelSet s;
|
||
|
|
||
|
for (int i = 0; m->channels[i] != 0; ++i)
|
||
|
s.addChannel (m->channels[i]);
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return AudioChannelSet::discreteChannels (arr.numberOfChannels);
|
||
|
}
|
||
|
|
||
|
static int32 channelSetToVstArrangementType (AudioChannelSet channels)
|
||
|
{
|
||
|
if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty;
|
||
|
if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono;
|
||
|
if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR;
|
||
|
if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC;
|
||
|
if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS;
|
||
|
if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS;
|
||
|
if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs;
|
||
|
if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs;
|
||
|
if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs;
|
||
|
if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs;
|
||
|
if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr;
|
||
|
if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr;
|
||
|
if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr;
|
||
|
if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc;
|
||
|
if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr;
|
||
|
if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc;
|
||
|
if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs;
|
||
|
|
||
|
if (channels == AudioChannelSet::disabled())
|
||
|
return vstSpeakerConfigTypeEmpty;
|
||
|
|
||
|
auto chans = channels.getChannelTypes();
|
||
|
|
||
|
for (auto* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m)
|
||
|
if (m->matches (chans))
|
||
|
return m->vst2;
|
||
|
|
||
|
return vstSpeakerConfigTypeUser;
|
||
|
}
|
||
|
|
||
|
static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerConfiguration& result)
|
||
|
{
|
||
|
result.type = channelSetToVstArrangementType (channels);
|
||
|
result.numberOfChannels = channels.size();
|
||
|
|
||
|
for (int i = 0; i < result.numberOfChannels; ++i)
|
||
|
{
|
||
|
auto& speaker = result.speakers[i];
|
||
|
|
||
|
zeromem (&speaker, sizeof (VstIndividualSpeakerInfo));
|
||
|
speaker.type = getSpeakerType (channels.getTypeOfChannel (i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const Mapping* getMappings() noexcept
|
||
|
{
|
||
|
static const Mapping mappings[] =
|
||
|
{
|
||
|
{ vstSpeakerConfigTypeMono, { centre, unknown } },
|
||
|
{ vstSpeakerConfigTypeLR, { left, right, unknown } },
|
||
|
{ vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } },
|
||
|
{ vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRC, { left, right, centre, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRS, { left, right, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } },
|
||
|
{ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } },
|
||
|
{ vstSpeakerConfigTypeEmpty, { unknown } }
|
||
|
};
|
||
|
|
||
|
return mappings;
|
||
|
}
|
||
|
|
||
|
static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft;
|
||
|
case AudioChannelSet::right: return vstIndividualSpeakerTypeRight;
|
||
|
case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre;
|
||
|
case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE;
|
||
|
case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround;
|
||
|
case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround;
|
||
|
case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre;
|
||
|
case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre;
|
||
|
case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround;
|
||
|
case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround;
|
||
|
case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround;
|
||
|
case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle;
|
||
|
case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft;
|
||
|
case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre;
|
||
|
case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight;
|
||
|
case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft;
|
||
|
case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre;
|
||
|
case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight;
|
||
|
case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2;
|
||
|
default: break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left;
|
||
|
case vstIndividualSpeakerTypeRight: return AudioChannelSet::right;
|
||
|
case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre;
|
||
|
case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE;
|
||
|
case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround;
|
||
|
case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround;
|
||
|
case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre;
|
||
|
case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre;
|
||
|
case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround;
|
||
|
case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear;
|
||
|
case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear;
|
||
|
case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle;
|
||
|
case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft;
|
||
|
case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre;
|
||
|
case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight;
|
||
|
case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft;
|
||
|
case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre;
|
||
|
case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight;
|
||
|
case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2;
|
||
|
default: break;
|
||
|
}
|
||
|
|
||
|
return AudioChannelSet::unknown;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void timerCallback() override
|
||
|
{
|
||
|
if (shouldDeleteEditor)
|
||
|
{
|
||
|
shouldDeleteEditor = false;
|
||
|
deleteEditor (true);
|
||
|
}
|
||
|
|
||
|
if (chunkMemoryTime > 0
|
||
|
&& chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000
|
||
|
&& ! recursionCheck)
|
||
|
{
|
||
|
chunkMemory.reset();
|
||
|
chunkMemoryTime = 0;
|
||
|
}
|
||
|
|
||
|
if (editorComp != nullptr)
|
||
|
editorComp->checkVisibility();
|
||
|
}
|
||
|
|
||
|
void createEditorComp()
|
||
|
{
|
||
|
if (hasShutdown || processor == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (editorComp == nullptr)
|
||
|
{
|
||
|
if (auto* ed = processor->createEditorIfNeeded())
|
||
|
{
|
||
|
vstEffect.flags |= vstEffectFlagHasEditor;
|
||
|
editorComp.reset (new EditorCompWrapper (*this, *ed));
|
||
|
|
||
|
#if ! (JUCE_MAC || JUCE_IOS)
|
||
|
ed->setScaleFactor (editorScaleFactor);
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vstEffect.flags &= ~vstEffectFlagHasEditor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
shouldDeleteEditor = false;
|
||
|
}
|
||
|
|
||
|
void deleteEditor (bool canDeleteLaterIfModal)
|
||
|
{
|
||
|
JUCE_AUTORELEASEPOOL
|
||
|
{
|
||
|
PopupMenu::dismissAllActiveMenus();
|
||
|
|
||
|
jassert (! recursionCheck);
|
||
|
ScopedValueSetter<bool> svs (recursionCheck, true, false);
|
||
|
|
||
|
if (editorComp != nullptr)
|
||
|
{
|
||
|
if (auto* modalComponent = Component::getCurrentlyModalComponent())
|
||
|
{
|
||
|
modalComponent->exitModalState (0);
|
||
|
|
||
|
if (canDeleteLaterIfModal)
|
||
|
{
|
||
|
shouldDeleteEditor = true;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
editorComp->detachHostWindow();
|
||
|
|
||
|
if (auto* ed = editorComp->getEditorComp())
|
||
|
processor->editorBeingDeleted (ed);
|
||
|
|
||
|
editorComp = nullptr;
|
||
|
|
||
|
// there's some kind of component currently modal, but the host
|
||
|
// is trying to delete our plugin. You should try to avoid this happening..
|
||
|
jassert (Component::getCurrentlyModalComponent() == nullptr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pointer_sized_int dispatcher (int32 opCode, VstOpCodeArguments args)
|
||
|
{
|
||
|
if (hasShutdown)
|
||
|
return 0;
|
||
|
|
||
|
switch (opCode)
|
||
|
{
|
||
|
case plugInOpcodeOpen: return handleOpen (args);
|
||
|
case plugInOpcodeClose: return handleClose (args);
|
||
|
case plugInOpcodeSetCurrentProgram: return handleSetCurrentProgram (args);
|
||
|
case plugInOpcodeGetCurrentProgram: return handleGetCurrentProgram (args);
|
||
|
case plugInOpcodeSetCurrentProgramName: return handleSetCurrentProgramName (args);
|
||
|
case plugInOpcodeGetCurrentProgramName: return handleGetCurrentProgramName (args);
|
||
|
case plugInOpcodeGetParameterLabel: return handleGetParameterLabel (args);
|
||
|
case plugInOpcodeGetParameterText: return handleGetParameterText (args);
|
||
|
case plugInOpcodeGetParameterName: return handleGetParameterName (args);
|
||
|
case plugInOpcodeSetSampleRate: return handleSetSampleRate (args);
|
||
|
case plugInOpcodeSetBlockSize: return handleSetBlockSize (args);
|
||
|
case plugInOpcodeResumeSuspend: return handleResumeSuspend (args);
|
||
|
case plugInOpcodeGetEditorBounds: return handleGetEditorBounds (args);
|
||
|
case plugInOpcodeOpenEditor: return handleOpenEditor (args);
|
||
|
case plugInOpcodeCloseEditor: return handleCloseEditor (args);
|
||
|
case plugInOpcodeIdentify: return (pointer_sized_int) ByteOrder::bigEndianInt ("NvEf");
|
||
|
case plugInOpcodeGetData: return handleGetData (args);
|
||
|
case plugInOpcodeSetData: return handleSetData (args);
|
||
|
case plugInOpcodePreAudioProcessingEvents: return handlePreAudioProcessingEvents (args);
|
||
|
case plugInOpcodeIsParameterAutomatable: return handleIsParameterAutomatable (args);
|
||
|
case plugInOpcodeParameterValueForText: return handleParameterValueForText (args);
|
||
|
case plugInOpcodeGetProgramName: return handleGetProgramName (args);
|
||
|
case plugInOpcodeGetInputPinProperties: return handleGetInputPinProperties (args);
|
||
|
case plugInOpcodeGetOutputPinProperties: return handleGetOutputPinProperties (args);
|
||
|
case plugInOpcodeGetPlugInCategory: return handleGetPlugInCategory (args);
|
||
|
case plugInOpcodeSetSpeakerConfiguration: return handleSetSpeakerConfiguration (args);
|
||
|
case plugInOpcodeSetBypass: return handleSetBypass (args);
|
||
|
case plugInOpcodeGetPlugInName: return handleGetPlugInName (args);
|
||
|
case plugInOpcodeGetManufacturerProductName: return handleGetPlugInName (args);
|
||
|
case plugInOpcodeGetManufacturerName: return handleGetManufacturerName (args);
|
||
|
case plugInOpcodeGetManufacturerVersion: return handleGetManufacturerVersion (args);
|
||
|
case plugInOpcodeManufacturerSpecific: return handleManufacturerSpecific (args);
|
||
|
case plugInOpcodeCanPlugInDo: return handleCanPlugInDo (args);
|
||
|
case plugInOpcodeGetTailSize: return handleGetTailSize (args);
|
||
|
case plugInOpcodeKeyboardFocusRequired: return handleKeyboardFocusRequired (args);
|
||
|
case plugInOpcodeGetVstInterfaceVersion: return handleGetVstInterfaceVersion (args);
|
||
|
case plugInOpcodeGetCurrentMidiProgram: return handleGetCurrentMidiProgram (args);
|
||
|
case plugInOpcodeGetSpeakerArrangement: return handleGetSpeakerConfiguration (args);
|
||
|
case plugInOpcodeSetNumberOfSamplesToProcess: return handleSetNumberOfSamplesToProcess (args);
|
||
|
case plugInOpcodeSetSampleFloatType: return handleSetSampleFloatType (args);
|
||
|
case pluginOpcodeGetNumMidiInputChannels: return handleGetNumMidiInputChannels();
|
||
|
case pluginOpcodeGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels();
|
||
|
default: return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static pointer_sized_int dispatcherCB (VstEffectInterface* vstInterface, int32 opCode, int32 index,
|
||
|
pointer_sized_int value, void* ptr, float opt)
|
||
|
{
|
||
|
auto* wrapper = getWrapper (vstInterface);
|
||
|
VstOpCodeArguments args = { index, value, ptr, opt };
|
||
|
|
||
|
if (opCode == plugInOpcodeClose)
|
||
|
{
|
||
|
wrapper->dispatcher (opCode, args);
|
||
|
delete wrapper;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return wrapper->dispatcher (opCode, args);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
// A component to hold the AudioProcessorEditor, and cope with some housekeeping
|
||
|
// chores when it changes or repaints.
|
||
|
struct EditorCompWrapper : public Component
|
||
|
{
|
||
|
EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor) : wrapper (w)
|
||
|
{
|
||
|
editor.setOpaque (true);
|
||
|
editor.setVisible (true);
|
||
|
setOpaque (true);
|
||
|
|
||
|
setTopLeftPosition (editor.getPosition());
|
||
|
editor.setTopLeftPosition (0, 0);
|
||
|
auto b = getLocalArea (&editor, editor.getLocalBounds());
|
||
|
setSize (b.getWidth(), b.getHeight());
|
||
|
|
||
|
addAndMakeVisible (editor);
|
||
|
|
||
|
#if JUCE_WINDOWS
|
||
|
if (! getHostType().isReceptor())
|
||
|
addMouseListener (this, true);
|
||
|
#endif
|
||
|
|
||
|
ignoreUnused (fakeMouseGenerator);
|
||
|
}
|
||
|
|
||
|
~EditorCompWrapper()
|
||
|
{
|
||
|
deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may
|
||
|
// have been transferred to another parent which takes over ownership.
|
||
|
}
|
||
|
|
||
|
void paint (Graphics&) override {}
|
||
|
|
||
|
void getEditorBounds (VstEditorBounds& bounds)
|
||
|
{
|
||
|
auto b = getSizeToContainChild();
|
||
|
|
||
|
bounds.upper = 0;
|
||
|
bounds.leftmost = 0;
|
||
|
bounds.lower = (int16) b.getHeight();
|
||
|
bounds.rightmost = (int16) b.getWidth();
|
||
|
}
|
||
|
|
||
|
void attachToHost (VstOpCodeArguments args)
|
||
|
{
|
||
|
setOpaque (true);
|
||
|
setVisible (false);
|
||
|
|
||
|
#if JUCE_WINDOWS
|
||
|
addToDesktop (0, args.ptr);
|
||
|
hostWindow = (HWND) args.ptr;
|
||
|
#elif JUCE_LINUX
|
||
|
addToDesktop (0, args.ptr);
|
||
|
hostWindow = (Window) args.ptr;
|
||
|
XReparentWindow (display.display, (Window) getWindowHandle(), hostWindow, 0, 0);
|
||
|
#else
|
||
|
hostWindow = attachComponentToWindowRefVST (this, args.ptr, wrapper.useNSView);
|
||
|
#endif
|
||
|
|
||
|
setVisible (true);
|
||
|
}
|
||
|
|
||
|
void detachHostWindow()
|
||
|
{
|
||
|
#if JUCE_MAC
|
||
|
if (hostWindow != 0)
|
||
|
{
|
||
|
detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView);
|
||
|
hostWindow = 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if JUCE_LINUX
|
||
|
hostWindow = 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void checkVisibility()
|
||
|
{
|
||
|
#if JUCE_MAC
|
||
|
if (hostWindow != 0)
|
||
|
checkWindowVisibilityVST (hostWindow, this, wrapper.useNSView);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
AudioProcessorEditor* getEditorComp() const noexcept
|
||
|
{
|
||
|
return dynamic_cast<AudioProcessorEditor*> (getChildComponent(0));
|
||
|
}
|
||
|
|
||
|
void resized() override
|
||
|
{
|
||
|
if (auto* ed = getEditorComp())
|
||
|
{
|
||
|
ed->setTopLeftPosition (0, 0);
|
||
|
|
||
|
if (shouldResizeEditor)
|
||
|
ed->setBounds (ed->getLocalArea (this, getLocalBounds()));
|
||
|
|
||
|
if (! getHostType().isBitwigStudio())
|
||
|
updateWindowSize (false);
|
||
|
}
|
||
|
|
||
|
#if JUCE_MAC && ! JUCE_64BIT
|
||
|
if (! wrapper.useNSView)
|
||
|
updateEditorCompBoundsVST (this);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void childBoundsChanged (Component*) override
|
||
|
{
|
||
|
updateWindowSize (false);
|
||
|
}
|
||
|
|
||
|
juce::Rectangle<int> getSizeToContainChild()
|
||
|
{
|
||
|
if (auto* ed = getEditorComp())
|
||
|
return getLocalArea (ed, ed->getLocalBounds());
|
||
|
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
void updateWindowSize (bool resizeEditor)
|
||
|
{
|
||
|
if (! isInSizeWindow)
|
||
|
{
|
||
|
if (auto* ed = getEditorComp())
|
||
|
{
|
||
|
ed->setTopLeftPosition (0, 0);
|
||
|
auto pos = getSizeToContainChild();
|
||
|
|
||
|
#if JUCE_MAC
|
||
|
if (wrapper.useNSView)
|
||
|
setTopLeftPosition (0, getHeight() - pos.getHeight());
|
||
|
#endif
|
||
|
|
||
|
resizeHostWindow (pos.getWidth(), pos.getHeight());
|
||
|
|
||
|
#if ! JUCE_LINUX // setSize() on linux causes renoise and energyxt to fail.
|
||
|
if (! resizeEditor) // this is needed to prevent an infinite resizing loop due to coordinate rounding
|
||
|
shouldResizeEditor = false;
|
||
|
|
||
|
setSize (pos.getWidth(), pos.getHeight());
|
||
|
|
||
|
shouldResizeEditor = true;
|
||
|
#else
|
||
|
ignoreUnused (resizeEditor);
|
||
|
XResizeWindow (display.display, (Window) getWindowHandle(), pos.getWidth(), pos.getHeight());
|
||
|
#endif
|
||
|
|
||
|
#if JUCE_MAC
|
||
|
resizeHostWindow (pos.getWidth(), pos.getHeight()); // (doing this a second time seems to be necessary in tracktion)
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void resizeHostWindow (int newWidth, int newHeight)
|
||
|
{
|
||
|
bool sizeWasSuccessful = false;
|
||
|
|
||
|
if (auto host = wrapper.hostCallback)
|
||
|
{
|
||
|
auto status = host (wrapper.getVstEffectInterface(), hostOpcodeCanHostDo, 0, 0, const_cast<char*> ("sizeWindow"), 0);
|
||
|
|
||
|
if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive())
|
||
|
{
|
||
|
isInSizeWindow = true;
|
||
|
sizeWasSuccessful = (host (wrapper.getVstEffectInterface(), hostOpcodeWindowSize, newWidth, newHeight, 0, 0) != 0);
|
||
|
isInSizeWindow = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! sizeWasSuccessful)
|
||
|
{
|
||
|
// some hosts don't support the sizeWindow call, so do it manually..
|
||
|
#if JUCE_MAC
|
||
|
setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView);
|
||
|
|
||
|
#elif JUCE_LINUX
|
||
|
// (Currently, all linux hosts support sizeWindow, so this should never need to happen)
|
||
|
setSize (newWidth, newHeight);
|
||
|
|
||
|
#else
|
||
|
int dw = 0;
|
||
|
int dh = 0;
|
||
|
const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME);
|
||
|
|
||
|
HWND w = (HWND) getWindowHandle();
|
||
|
|
||
|
while (w != 0)
|
||
|
{
|
||
|
HWND parent = getWindowParent (w);
|
||
|
|
||
|
if (parent == 0)
|
||
|
break;
|
||
|
|
||
|
TCHAR windowType [32] = { 0 };
|
||
|
GetClassName (parent, windowType, 31);
|
||
|
|
||
|
if (String (windowType).equalsIgnoreCase ("MDIClient"))
|
||
|
break;
|
||
|
|
||
|
RECT windowPos, parentPos;
|
||
|
GetWindowRect (w, &windowPos);
|
||
|
GetWindowRect (parent, &parentPos);
|
||
|
|
||
|
SetWindowPos (w, 0, 0, 0, newWidth + dw, newHeight + dh,
|
||
|
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||
|
|
||
|
dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left);
|
||
|
dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top);
|
||
|
|
||
|
w = parent;
|
||
|
|
||
|
if (dw == 2 * frameThickness)
|
||
|
break;
|
||
|
|
||
|
if (dw > 100 || dh > 100)
|
||
|
w = 0;
|
||
|
}
|
||
|
|
||
|
if (w != 0)
|
||
|
SetWindowPos (w, 0, 0, 0, newWidth + dw, newHeight + dh,
|
||
|
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (auto* peer = getPeer())
|
||
|
{
|
||
|
peer->handleMovedOrResized();
|
||
|
repaint();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if JUCE_WINDOWS
|
||
|
void mouseDown (const MouseEvent&) override
|
||
|
{
|
||
|
broughtToFront();
|
||
|
}
|
||
|
|
||
|
void broughtToFront() override
|
||
|
{
|
||
|
// for hosts like nuendo, need to also pop the MDI container to the
|
||
|
// front when our comp is clicked on.
|
||
|
if (! isCurrentlyBlockedByAnotherModalComponent())
|
||
|
if (HWND parent = findMDIParentOf ((HWND) getWindowHandle()))
|
||
|
SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if JUCE_MAC
|
||
|
bool keyPressed (const KeyPress&) override
|
||
|
{
|
||
|
// If we have an unused keypress, move the key-focus to a host window
|
||
|
// and re-inject the event..
|
||
|
return forwardCurrentKeyEventToHostVST (this, wrapper.useNSView);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//==============================================================================
|
||
|
JuceVSTWrapper& wrapper;
|
||
|
FakeMouseMoveGenerator fakeMouseGenerator;
|
||
|
bool isInSizeWindow = false;
|
||
|
bool shouldResizeEditor = true;
|
||
|
|
||
|
#if JUCE_MAC
|
||
|
void* hostWindow = {};
|
||
|
#elif JUCE_LINUX
|
||
|
ScopedXDisplay display;
|
||
|
Window hostWindow = {};
|
||
|
#else
|
||
|
HWND hostWindow = {};
|
||
|
WindowsHooks hooks;
|
||
|
#endif
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
private:
|
||
|
VstHostCallback hostCallback;
|
||
|
AudioProcessor* processor = {};
|
||
|
double sampleRate = 44100.0;
|
||
|
int32 blockSize = 1024;
|
||
|
VstEffectInterface vstEffect;
|
||
|
juce::MemoryBlock chunkMemory;
|
||
|
juce::uint32 chunkMemoryTime = 0;
|
||
|
std::unique_ptr<EditorCompWrapper> editorComp;
|
||
|
VstEditorBounds editorBounds;
|
||
|
MidiBuffer midiEvents;
|
||
|
VSTMidiEventList outgoingEvents;
|
||
|
float editorScaleFactor = 1.0f;
|
||
|
|
||
|
LegacyAudioParametersWrapper juceParameters;
|
||
|
|
||
|
bool isProcessing = false, isBypassed = false, hasShutdown = false;
|
||
|
bool firstProcessCallback = true, shouldDeleteEditor = false;
|
||
|
|
||
|
#if JUCE_64BIT
|
||
|
bool useNSView = true;
|
||
|
#else
|
||
|
bool useNSView = false;
|
||
|
#endif
|
||
|
|
||
|
VstTempBuffers<float> floatTempBuffers;
|
||
|
VstTempBuffers<double> doubleTempBuffers;
|
||
|
int maxNumInChannels = 0, maxNumOutChannels = 0;
|
||
|
|
||
|
HeapBlock<VstSpeakerConfiguration> cachedInArrangement, cachedOutArrangement;
|
||
|
|
||
|
ThreadLocalValue<bool> inParameterChangedCallback;
|
||
|
|
||
|
static JuceVSTWrapper* getWrapper (VstEffectInterface* v) noexcept { return static_cast<JuceVSTWrapper*> (v->effectPointer); }
|
||
|
|
||
|
bool isProcessLevelOffline()
|
||
|
{
|
||
|
return hostCallback != nullptr
|
||
|
&& (int32) hostCallback (&vstEffect, hostOpcodeGetCurrentAudioProcessingLevel, 0, 0, 0, 0) == 4;
|
||
|
}
|
||
|
|
||
|
static inline int32 convertHexVersionToDecimal (const unsigned int hexVersion)
|
||
|
{
|
||
|
#if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY
|
||
|
return (int32) hexVersion;
|
||
|
#else
|
||
|
// Currently, only Cubase displays the version number to the user
|
||
|
// We are hoping here that when other DAWs start to display the version
|
||
|
// number, that they do so according to yfede's encoding table in the link
|
||
|
// below. If not, then this code will need an if (isSteinberg()) in the
|
||
|
// future.
|
||
|
int major = (hexVersion >> 16) & 0xff;
|
||
|
int minor = (hexVersion >> 8) & 0xff;
|
||
|
int bugfix = hexVersion & 0xff;
|
||
|
|
||
|
// for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867
|
||
|
|
||
|
// Encoding B
|
||
|
if (major < 1)
|
||
|
return major * 1000 + minor * 100 + bugfix * 10;
|
||
|
|
||
|
// Encoding E
|
||
|
if (major > 100)
|
||
|
return major * 10000000 + minor * 100000 + bugfix * 1000;
|
||
|
|
||
|
// Encoding D
|
||
|
return static_cast<int32> (hexVersion);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
#if JUCE_WINDOWS
|
||
|
// Workarounds for hosts which attempt to open editor windows on a non-GUI thread.. (Grrrr...)
|
||
|
static void checkWhetherMessageThreadIsCorrect()
|
||
|
{
|
||
|
auto host = getHostType();
|
||
|
|
||
|
if (host.isWavelab() || host.isCubaseBridged() || host.isPremiere())
|
||
|
{
|
||
|
if (! messageThreadIsDefinitelyCorrect)
|
||
|
{
|
||
|
MessageManager::getInstance()->setCurrentThreadAsMessageThread();
|
||
|
|
||
|
struct MessageThreadCallback : public CallbackMessage
|
||
|
{
|
||
|
MessageThreadCallback (bool& tr) : triggered (tr) {}
|
||
|
void messageCallback() override { triggered = true; }
|
||
|
|
||
|
bool& triggered;
|
||
|
};
|
||
|
|
||
|
(new MessageThreadCallback (messageThreadIsDefinitelyCorrect))->post();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
static void checkWhetherMessageThreadIsCorrect() {}
|
||
|
#endif
|
||
|
|
||
|
//==============================================================================
|
||
|
template <typename FloatType>
|
||
|
void deleteTempChannels (VstTempBuffers<FloatType>& tmpBuffers)
|
||
|
{
|
||
|
tmpBuffers.release();
|
||
|
|
||
|
if (processor != nullptr)
|
||
|
tmpBuffers.tempChannels.insertMultiple (0, nullptr, vstEffect.numInputChannels
|
||
|
+ vstEffect.numOutputChannels);
|
||
|
}
|
||
|
|
||
|
void deleteTempChannels()
|
||
|
{
|
||
|
deleteTempChannels (floatTempBuffers);
|
||
|
deleteTempChannels (doubleTempBuffers);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void findMaxTotalChannels (int& maxTotalIns, int& maxTotalOuts)
|
||
|
{
|
||
|
#ifdef JucePlugin_PreferredChannelConfigurations
|
||
|
int configs[][2] = { JucePlugin_PreferredChannelConfigurations };
|
||
|
maxTotalIns = maxTotalOuts = 0;
|
||
|
|
||
|
for (auto& config : configs)
|
||
|
{
|
||
|
maxTotalIns = jmax (maxTotalIns, config[0]);
|
||
|
maxTotalOuts = jmax (maxTotalOuts, config[1]);
|
||
|
}
|
||
|
#else
|
||
|
auto numInputBuses = processor->getBusCount (true);
|
||
|
auto numOutputBuses = processor->getBusCount (false);
|
||
|
|
||
|
if (numInputBuses > 1 || numOutputBuses > 1)
|
||
|
{
|
||
|
maxTotalIns = maxTotalOuts = 0;
|
||
|
|
||
|
for (int i = 0; i < numInputBuses; ++i)
|
||
|
maxTotalIns += processor->getChannelCountOfBus (true, i);
|
||
|
|
||
|
for (int i = 0; i < numOutputBuses; ++i)
|
||
|
maxTotalOuts += processor->getChannelCountOfBus (false, i);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
maxTotalIns = numInputBuses > 0 ? processor->getBus (true, 0)->getMaxSupportedChannels (64) : 0;
|
||
|
maxTotalOuts = numOutputBuses > 0 ? processor->getBus (false, 0)->getMaxSupportedChannels (64) : 0;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
bool pluginHasSidechainsOrAuxs() const { return (processor->getBusCount (true) > 1 || processor->getBusCount (false) > 1); }
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Host to plug-in calls. */
|
||
|
|
||
|
pointer_sized_int handleOpen (VstOpCodeArguments)
|
||
|
{
|
||
|
// Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here.
|
||
|
if (processor->hasEditor())
|
||
|
vstEffect.flags |= vstEffectFlagHasEditor;
|
||
|
else
|
||
|
vstEffect.flags &= ~vstEffectFlagHasEditor;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleClose (VstOpCodeArguments)
|
||
|
{
|
||
|
// Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here.
|
||
|
stopTimer();
|
||
|
|
||
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||
|
deleteEditor (false);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetCurrentProgram (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor != nullptr && isPositiveAndBelow ((int) args.value, processor->getNumPrograms()))
|
||
|
processor->setCurrentProgram ((int) args.value);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetCurrentProgram (VstOpCodeArguments)
|
||
|
{
|
||
|
return (processor != nullptr && processor->getNumPrograms() > 0 ? processor->getCurrentProgram() : 0);
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetCurrentProgramName (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor != nullptr && processor->getNumPrograms() > 0)
|
||
|
processor->changeProgramName (processor->getCurrentProgram(), (char*) args.ptr);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetCurrentProgramName (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor != nullptr && processor->getNumPrograms() > 0)
|
||
|
processor->getProgramName (processor->getCurrentProgram()).copyToUTF8 ((char*) args.ptr, 24 + 1);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetParameterLabel (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (args.index))
|
||
|
{
|
||
|
// length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more.
|
||
|
param->getLabel().copyToUTF8 ((char*) args.ptr, 24 + 1);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetParameterText (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (args.index))
|
||
|
{
|
||
|
// length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more.
|
||
|
param->getCurrentValueAsText().copyToUTF8 ((char*) args.ptr, 24 + 1);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetParameterName (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (args.index))
|
||
|
{
|
||
|
// length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more.
|
||
|
param->getName (32).copyToUTF8 ((char*) args.ptr, 32 + 1);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetSampleRate (VstOpCodeArguments args)
|
||
|
{
|
||
|
sampleRate = args.opt;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetBlockSize (VstOpCodeArguments args)
|
||
|
{
|
||
|
blockSize = (int32) args.value;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleResumeSuspend (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (args.value)
|
||
|
resume();
|
||
|
else
|
||
|
suspend();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetEditorBounds (VstOpCodeArguments args)
|
||
|
{
|
||
|
checkWhetherMessageThreadIsCorrect();
|
||
|
const MessageManagerLock mmLock;
|
||
|
createEditorComp();
|
||
|
|
||
|
if (editorComp != nullptr)
|
||
|
{
|
||
|
editorComp->getEditorBounds (editorBounds);
|
||
|
*((VstEditorBounds**) args.ptr) = &editorBounds;
|
||
|
return (pointer_sized_int) &editorBounds;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleOpenEditor (VstOpCodeArguments args)
|
||
|
{
|
||
|
checkWhetherMessageThreadIsCorrect();
|
||
|
const MessageManagerLock mmLock;
|
||
|
jassert (! recursionCheck);
|
||
|
|
||
|
startTimerHz (4); // performs misc housekeeping chores
|
||
|
|
||
|
deleteEditor (true);
|
||
|
createEditorComp();
|
||
|
|
||
|
if (editorComp != nullptr)
|
||
|
{
|
||
|
editorComp->attachToHost (args);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleCloseEditor (VstOpCodeArguments)
|
||
|
{
|
||
|
checkWhetherMessageThreadIsCorrect();
|
||
|
const MessageManagerLock mmLock;
|
||
|
deleteEditor (true);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetData (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor == nullptr)
|
||
|
return 0;
|
||
|
|
||
|
auto data = (void**) args.ptr;
|
||
|
bool onlyStoreCurrentProgramData = (args.index != 0);
|
||
|
|
||
|
chunkMemory.reset();
|
||
|
if (onlyStoreCurrentProgramData)
|
||
|
processor->getCurrentProgramStateInformation (chunkMemory);
|
||
|
else
|
||
|
processor->getStateInformation (chunkMemory);
|
||
|
|
||
|
*data = (void*) chunkMemory.getData();
|
||
|
|
||
|
// because the chunk is only needed temporarily by the host (or at least you'd
|
||
|
// hope so) we'll give it a while and then free it in the timer callback.
|
||
|
chunkMemoryTime = juce::Time::getApproximateMillisecondCounter();
|
||
|
|
||
|
return (int32) chunkMemory.getSize();
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetData (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor != nullptr)
|
||
|
{
|
||
|
void* data = args.ptr;
|
||
|
int32 byteSize = (int32) args.value;
|
||
|
bool onlyRestoreCurrentProgramData = (args.index != 0);
|
||
|
|
||
|
chunkMemory.reset();
|
||
|
chunkMemoryTime = 0;
|
||
|
|
||
|
if (byteSize > 0 && data != nullptr)
|
||
|
{
|
||
|
if (onlyRestoreCurrentProgramData)
|
||
|
processor->setCurrentProgramStateInformation (data, byteSize);
|
||
|
else
|
||
|
processor->setStateInformation (data, byteSize);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handlePreAudioProcessingEvents (VstOpCodeArguments args)
|
||
|
{
|
||
|
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect
|
||
|
VSTMidiEventList::addEventsToMidiBuffer ((VstEventBlock*) args.ptr, midiEvents);
|
||
|
return 1;
|
||
|
#else
|
||
|
ignoreUnused (args);
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleIsParameterAutomatable (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (args.index))
|
||
|
{
|
||
|
const bool isMeter = (((param->getCategory() & 0xffff0000) >> 16) == 2);
|
||
|
return (param->isAutomatable() && (! isMeter) ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleParameterValueForText (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex (args.index))
|
||
|
{
|
||
|
if (! LegacyAudioParameter::isLegacy (param))
|
||
|
{
|
||
|
auto value = param->getValueForText (String::fromUTF8 ((char*) args.ptr));
|
||
|
param->setValue (value);
|
||
|
|
||
|
inParameterChangedCallback = true;
|
||
|
param->sendValueChangedMessageToListeners (value);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetProgramName (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (processor != nullptr && isPositiveAndBelow (args.index, processor->getNumPrograms()))
|
||
|
{
|
||
|
processor->getProgramName (args.index).copyToUTF8 ((char*) args.ptr, 24 + 1);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args)
|
||
|
{
|
||
|
return (processor != nullptr && getPinProperties (*(VstPinInfo*) args.ptr, true, args.index)) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetOutputPinProperties (VstOpCodeArguments args)
|
||
|
{
|
||
|
return (processor != nullptr && getPinProperties (*(VstPinInfo*) args.ptr, false, args.index)) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments)
|
||
|
{
|
||
|
return JucePlugin_VSTCategory;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args)
|
||
|
{
|
||
|
auto* pluginInput = reinterpret_cast<VstSpeakerConfiguration*> (args.value);
|
||
|
auto* pluginOutput = reinterpret_cast<VstSpeakerConfiguration*> (args.ptr);
|
||
|
|
||
|
if (processor->isMidiEffect())
|
||
|
return 0;
|
||
|
|
||
|
auto numIns = processor->getBusCount (true);
|
||
|
auto numOuts = processor->getBusCount (false);
|
||
|
|
||
|
if (pluginInput != nullptr && pluginInput->type >= 0)
|
||
|
{
|
||
|
// inconsistent request?
|
||
|
if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput).size() != pluginInput->numberOfChannels)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (pluginOutput != nullptr && pluginOutput->type >= 0)
|
||
|
{
|
||
|
// inconsistent request?
|
||
|
if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput).size() != pluginOutput->numberOfChannels)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (pluginInput != nullptr && pluginInput->numberOfChannels > 0 && numIns == 0)
|
||
|
return 0;
|
||
|
|
||
|
if (pluginOutput != nullptr && pluginOutput->numberOfChannels > 0 && numOuts == 0)
|
||
|
return 0;
|
||
|
|
||
|
auto layouts = processor->getBusesLayout();
|
||
|
|
||
|
if (pluginInput != nullptr && pluginInput-> numberOfChannels >= 0 && numIns > 0)
|
||
|
layouts.getChannelSet (true, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput);
|
||
|
|
||
|
if (pluginOutput != nullptr && pluginOutput->numberOfChannels >= 0 && numOuts > 0)
|
||
|
layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput);
|
||
|
|
||
|
#ifdef JucePlugin_PreferredChannelConfigurations
|
||
|
short configs[][2] = { JucePlugin_PreferredChannelConfigurations };
|
||
|
if (! AudioProcessor::containsLayout (layouts, configs))
|
||
|
return 0;
|
||
|
#endif
|
||
|
|
||
|
return processor->setBusesLayout (layouts) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetBypass (VstOpCodeArguments args)
|
||
|
{
|
||
|
isBypassed = (args.value != 0);
|
||
|
|
||
|
if (auto* bypass = processor->getBypassParameter())
|
||
|
bypass->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetPlugInName (VstOpCodeArguments args)
|
||
|
{
|
||
|
String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetManufacturerName (VstOpCodeArguments args)
|
||
|
{
|
||
|
String (JucePlugin_Manufacturer).copyToUTF8 ((char*) args.ptr, 64 + 1);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments)
|
||
|
{
|
||
|
return convertHexVersionToDecimal (JucePlugin_VersionCode);
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleManufacturerSpecific (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (handleManufacturerSpecificVST2Opcode (args.index, args.value, args.ptr, args.opt))
|
||
|
return 1;
|
||
|
|
||
|
if (args.index == presonusVendorID && args.value == presonusSetContentScaleFactor)
|
||
|
return handleSetContentScaleFactor (args.opt);
|
||
|
|
||
|
if (args.index == plugInOpcodeGetParameterText)
|
||
|
return handleCockosGetParameterText (args.value, args.ptr, args.opt);
|
||
|
|
||
|
if (auto callbackHandler = dynamic_cast<VSTCallbackHandler*> (processor))
|
||
|
return callbackHandler->handleVstManufacturerSpecific (args.index, args.value, args.ptr, args.opt);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleCanPlugInDo (VstOpCodeArguments args)
|
||
|
{
|
||
|
auto text = (const char*) args.ptr;
|
||
|
auto matches = [=](const char* s) { return strcmp (text, s) == 0; };
|
||
|
|
||
|
if (matches ("receiveVstEvents")
|
||
|
|| matches ("receiveVstMidiEvent")
|
||
|
|| matches ("receiveVstMidiEvents"))
|
||
|
{
|
||
|
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect
|
||
|
return 1;
|
||
|
#else
|
||
|
return -1;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (matches ("sendVstEvents")
|
||
|
|| matches ("sendVstMidiEvent")
|
||
|
|| matches ("sendVstMidiEvents"))
|
||
|
{
|
||
|
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect
|
||
|
return 1;
|
||
|
#else
|
||
|
return -1;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (matches ("receiveVstTimeInfo")
|
||
|
|| matches ("conformsToWindowRules")
|
||
|
|| matches ("supportsViewDpiScaling")
|
||
|
|| matches ("bypass"))
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// This tells Wavelab to use the UI thread to invoke open/close,
|
||
|
// like all other hosts do.
|
||
|
if (matches ("openCloseAnyThread"))
|
||
|
return -1;
|
||
|
|
||
|
if (matches ("MPE"))
|
||
|
return processor->supportsMPE() ? 1 : 0;
|
||
|
|
||
|
#if JUCE_MAC
|
||
|
if (matches ("hasCockosViewAsConfig"))
|
||
|
{
|
||
|
useNSView = true;
|
||
|
return (int32) 0xbeef0000;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (matches ("hasCockosExtensions"))
|
||
|
return (int32) 0xbeef0000;
|
||
|
|
||
|
if (auto callbackHandler = dynamic_cast<VSTCallbackHandler*> (processor))
|
||
|
return callbackHandler->handleVstPluginCanDo (args.index, args.value, args.ptr, args.opt);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetTailSize (VstOpCodeArguments)
|
||
|
{
|
||
|
if (processor != nullptr)
|
||
|
return (pointer_sized_int) (processor->getTailLengthSeconds() * sampleRate);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleKeyboardFocusRequired (VstOpCodeArguments)
|
||
|
{
|
||
|
return (JucePlugin_EditorRequiresKeyboardFocus != 0) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments)
|
||
|
{
|
||
|
return juceVstInterfaceVersion;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args)
|
||
|
{
|
||
|
auto** pluginInput = reinterpret_cast<VstSpeakerConfiguration**> (args.value);
|
||
|
auto** pluginOutput = reinterpret_cast<VstSpeakerConfiguration**> (args.ptr);
|
||
|
|
||
|
if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect())
|
||
|
return false;
|
||
|
|
||
|
auto inputLayout = processor->getChannelLayoutOfBus (true, 0);
|
||
|
auto outputLayout = processor->getChannelLayoutOfBus (false, 0);
|
||
|
|
||
|
auto speakerBaseSize = sizeof (VstSpeakerConfiguration) - (sizeof (VstIndividualSpeakerInfo) * 8);
|
||
|
|
||
|
cachedInArrangement .malloc (speakerBaseSize + (static_cast<std::size_t> (inputLayout. size()) * sizeof (VstSpeakerConfiguration)), 1);
|
||
|
cachedOutArrangement.malloc (speakerBaseSize + (static_cast<std::size_t> (outputLayout.size()) * sizeof (VstSpeakerConfiguration)), 1);
|
||
|
|
||
|
*pluginInput = cachedInArrangement. getData();
|
||
|
*pluginOutput = cachedOutArrangement.getData();
|
||
|
|
||
|
SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (true, 0), **pluginInput);
|
||
|
SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (false, 0), **pluginOutput);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetNumberOfSamplesToProcess (VstOpCodeArguments args)
|
||
|
{
|
||
|
return args.value;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetSampleFloatType (VstOpCodeArguments args)
|
||
|
{
|
||
|
if (! isProcessing)
|
||
|
{
|
||
|
if (processor != nullptr)
|
||
|
{
|
||
|
processor->setProcessingPrecision ((args.value == vstProcessingSampleTypeDouble
|
||
|
&& processor->supportsDoublePrecisionProcessing())
|
||
|
? AudioProcessor::doublePrecision
|
||
|
: AudioProcessor::singlePrecision);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleSetContentScaleFactor (float scale)
|
||
|
{
|
||
|
if (editorScaleFactor != scale)
|
||
|
{
|
||
|
editorScaleFactor = scale;
|
||
|
|
||
|
#if ! (JUCE_MAC || JUCE_IOS)
|
||
|
if (editorComp != nullptr)
|
||
|
{
|
||
|
if (auto* ed = editorComp->getEditorComp())
|
||
|
ed->setScaleFactor (editorScaleFactor);
|
||
|
|
||
|
if (editorComp != nullptr)
|
||
|
editorComp->updateWindowSize (true);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleCockosGetParameterText (pointer_sized_int paramIndex,
|
||
|
void* dest,
|
||
|
float value)
|
||
|
{
|
||
|
if (processor != nullptr && dest != nullptr)
|
||
|
{
|
||
|
if (auto* param = juceParameters.getParamForIndex ((int) paramIndex))
|
||
|
{
|
||
|
if (! LegacyAudioParameter::isLegacy (param))
|
||
|
{
|
||
|
String text (param->getText (value, 1024));
|
||
|
memcpy (dest, text.toRawUTF8(), ((size_t) text.length()) + 1);
|
||
|
return 0xbeef;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
pointer_sized_int handleGetNumMidiInputChannels()
|
||
|
{
|
||
|
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect
|
||
|
return 16;
|
||
|
#else
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
pointer_sized_int handleGetNumMidiOutputChannels()
|
||
|
{
|
||
|
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect
|
||
|
return 16;
|
||
|
#else
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper)
|
||
|
};
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
namespace
|
||
|
{
|
||
|
VstEffectInterface* pluginEntryPoint (VstHostCallback audioMaster)
|
||
|
{
|
||
|
JUCE_AUTORELEASEPOOL
|
||
|
{
|
||
|
initialiseJuce_GUI();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (audioMaster (0, hostOpcodeVstVersion, 0, 0, 0, 0) != 0)
|
||
|
{
|
||
|
#if JUCE_LINUX
|
||
|
MessageManagerLock mmLock;
|
||
|
#endif
|
||
|
|
||
|
auto* processor = createPluginFilterOfType (AudioProcessor::wrapperType_VST);
|
||
|
auto* wrapper = new JuceVSTWrapper (audioMaster, processor);
|
||
|
return wrapper->getVstEffectInterface();
|
||
|
}
|
||
|
}
|
||
|
catch (...)
|
||
|
{}
|
||
|
}
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if ! JUCE_WINDOWS
|
||
|
#define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default")))
|
||
|
#endif
|
||
|
|
||
|
//==============================================================================
|
||
|
// Mac startup code..
|
||
|
#if JUCE_MAC
|
||
|
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* VSTPluginMain (VstHostCallback audioMaster);
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* VSTPluginMain (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
initialiseMacVST();
|
||
|
return pluginEntryPoint (audioMaster);
|
||
|
}
|
||
|
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* main_macho (VstHostCallback audioMaster);
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* main_macho (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
initialiseMacVST();
|
||
|
return pluginEntryPoint (audioMaster);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
// Linux startup code..
|
||
|
#elif JUCE_LINUX
|
||
|
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* VSTPluginMain (VstHostCallback audioMaster);
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* VSTPluginMain (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
SharedMessageThread::getInstance();
|
||
|
return pluginEntryPoint (audioMaster);
|
||
|
}
|
||
|
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* main_plugin (VstHostCallback audioMaster) asm ("main");
|
||
|
JUCE_EXPORTED_FUNCTION VstEffectInterface* main_plugin (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
return VSTPluginMain (audioMaster);
|
||
|
}
|
||
|
|
||
|
// don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash!
|
||
|
__attribute__((constructor)) void myPluginInit() {}
|
||
|
__attribute__((destructor)) void myPluginFini() {}
|
||
|
|
||
|
//==============================================================================
|
||
|
// Win32 startup code..
|
||
|
#else
|
||
|
|
||
|
extern "C" __declspec (dllexport) VstEffectInterface* VSTPluginMain (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
return pluginEntryPoint (audioMaster);
|
||
|
}
|
||
|
|
||
|
#ifndef JUCE_64BIT // (can't compile this on win64, but it's not needed anyway with VST2.4)
|
||
|
extern "C" __declspec (dllexport) int main (VstHostCallback audioMaster)
|
||
|
{
|
||
|
PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST;
|
||
|
|
||
|
return (int) pluginEntryPoint (audioMaster);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID)
|
||
|
{
|
||
|
if (reason == DLL_PROCESS_ATTACH)
|
||
|
Process::setCurrentModuleInstanceHandle (instance);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#endif
|