Browse Source

Add VST3 bypass support

tags/2021-05-28
hogliux 9 years ago
parent
commit
b7bca8ce5a
2 changed files with 231 additions and 29 deletions
  1. +226
    -29
      modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
  2. +5
    -0
      modules/juce_audio_processors/format_types/juce_VST3Common.h

+ 226
- 29
modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp View File

@@ -73,7 +73,7 @@ class JuceAudioProcessor : public FUnknown
{ {
public: public:
JuceAudioProcessor (AudioProcessor* source) noexcept JuceAudioProcessor (AudioProcessor* source) noexcept
: refCount (0), audioProcessor (source) {}
: isBypassed (false), refCount (0), audioProcessor (source) {}
virtual ~JuceAudioProcessor() {} virtual ~JuceAudioProcessor() {}
@@ -84,6 +84,8 @@ public:
static const FUID iid; static const FUID iid;
bool isBypassed;
private: private:
Atomic<int> refCount; Atomic<int> refCount;
ScopedPointer<AudioProcessor> audioProcessor; ScopedPointer<AudioProcessor> audioProcessor;
@@ -230,14 +232,104 @@ public:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param)
}; };
//==============================================================================
struct BypassParam : public Vst::Parameter
{
BypassParam (AudioProcessor& p, int index) : owner (p), paramIndex (index)
{
info.id = (Vst::ParamID) index;
toString128 (info.title, "Bypass");
toString128 (info.shortTitle, "Bypass");
toString128 (info.units, "");
info.stepCount = 2;
info.defaultNormalizedValue = 0.0f;
info.unitId = Vst::kRootUnitId;
info.flags = Vst::ParameterInfo::kIsBypass;
}
virtual ~BypassParam() {}
bool setNormalized (Vst::ParamValue v) override
{
bool bypass = (v != 0.0f);
v = (bypass ? 1.0f : 0.0f);
if (valueNormalized != v)
{
valueNormalized = v;
changed();
return true;
}
return false;
}
void toString (Vst::ParamValue value, Vst::String128 result) const override
{
bool bypass = (value != 0.0f);
toString128 (result, bypass ? "On" : "Off");
}
bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override
{
const String paramValueString (getStringFromVstTChars (text));
if (paramValueString.equalsIgnoreCase ("on")
|| paramValueString.equalsIgnoreCase ("yes")
|| paramValueString.equalsIgnoreCase ("true"))
{
outValueNormalized = 1.0f;
return true;
}
if (paramValueString.equalsIgnoreCase ("off")
|| paramValueString.equalsIgnoreCase ("no")
|| paramValueString.equalsIgnoreCase ("false"))
{
outValueNormalized = 0.0f;
return true;
}
var varValue = JSON::fromString (paramValueString);
if (varValue.isDouble() || varValue.isInt()
|| varValue.isInt64() || varValue.isBool())
{
double value = varValue;
outValueNormalized = (value != 0.0) ? 1.0f : 0.0f;
return true;
}
return false;
}
static String getStringFromVstTChars (const Vst::TChar* text)
{
return juce::String (juce::CharPointer_UTF16 (reinterpret_cast<const juce::CharPointer_UTF16::CharType*> (text)));
}
Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; }
Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; }
private:
AudioProcessor& owner;
int paramIndex;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassParam)
};
//============================================================================== //==============================================================================
tresult PLUGIN_API setComponentState (IBStream* stream) override tresult PLUGIN_API setComponentState (IBStream* stream) override
{ {
// Cubase and Nuendo need to inform the host of the current parameter values // Cubase and Nuendo need to inform the host of the current parameter values
if (AudioProcessor* const pluginInstance = getPluginInstance()) if (AudioProcessor* const pluginInstance = getPluginInstance())
{ {
for (int i = 0; i < pluginInstance->getNumParameters(); ++i)
const int numParameters = pluginInstance->getNumParameters();
for (int i = 0; i < numParameters; ++i)
setParamNormalized ((Vst::ParamID) i, (double) pluginInstance->getParameter (i)); setParamNormalized ((Vst::ParamID) i, (double) pluginInstance->getParameter (i));
setParamNormalized ((Vst::ParamID) numParameters, audioProcessor->isBypassed ? 1.0f : 0.0f);
} }
return Vst::EditController::setComponentState (stream); return Vst::EditController::setComponentState (stream);
@@ -274,7 +366,7 @@ public:
tresult PLUGIN_API getMidiControllerAssignment (Steinberg::int32 /*busIndex*/, Steinberg::int16 channel, tresult PLUGIN_API getMidiControllerAssignment (Steinberg::int32 /*busIndex*/, Steinberg::int16 channel,
Vst::CtrlNumber midiControllerNumber, Vst::ParamID& resultID) override Vst::CtrlNumber midiControllerNumber, Vst::ParamID& resultID) override
{ {
resultID = midiControllerToParameter[channel][midiControllerNumber];
resultID = (Vst::ParamID) midiControllerToParameter[channel][midiControllerNumber];
return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments
} }
@@ -366,17 +458,25 @@ private:
pluginInstance->addListener (this); pluginInstance->addListener (this);
if (parameters.getParameterCount() <= 0) if (parameters.getParameterCount() <= 0)
for (int i = 0; i < pluginInstance->getNumParameters(); ++i)
{
const int numParameters = pluginInstance->getNumParameters();
for (int i = 0; i < numParameters; ++i)
parameters.addParameter (new Param (*pluginInstance, i)); parameters.addParameter (new Param (*pluginInstance, i));
initialiseMidiControllerMappings (pluginInstance->getNumParameters());
parameters.addParameter (new BypassParam (*pluginInstance, numParameters));
}
// We need to account for the bypass parameter in the numParameters passed to
// the next function
initialiseMidiControllerMappings (pluginInstance->getNumParameters() + 1);
audioProcessorChanged (pluginInstance); audioProcessorChanged (pluginInstance);
} }
} }
void initialiseMidiControllerMappings (const int numParameters)
void initialiseMidiControllerMappings (const int numVstParameters)
{ {
parameterToMidiControllerOffset = numParameters;
parameterToMidiControllerOffset = numVstParameters;
for (int c = 0, p = 0; c < numMIDIChannels; ++c) for (int c = 0, p = 0; c < numMIDIChannels; ++c)
{ {
@@ -387,7 +487,7 @@ private:
parameterToMidiController[p].ctrlNumber = i; parameterToMidiController[p].ctrlNumber = i;
parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)), parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)),
p + parameterToMidiControllerOffset, 0, 0, 0,
(Vst::ParamID) (p + parameterToMidiControllerOffset), 0, 0, 0,
Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId)); Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId));
} }
} }
@@ -655,6 +755,8 @@ public:
processSetup.sampleRate = 44100.0; processSetup.sampleRate = 44100.0;
processSetup.symbolicSampleSize = Vst::kSample32; processSetup.symbolicSampleSize = Vst::kSample32;
vstBypassParameterId = pluginInstance->getNumParameters();
pluginInstance->setPlayHead (this); pluginInstance->setPlayHead (this);
} }
@@ -852,12 +954,92 @@ public:
tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; }
tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; }
bool isBypassed() { return comPluginInstance->isBypassed; }
void setBypassed (bool bypassed) { comPluginInstance->isBypassed = bypassed; }
//==============================================================================
void writeJucePrivateStateInformation (MemoryOutputStream& out)
{
ValueTree privateData (kJucePrivateDataIdentifier);
// for now we only store the bypass value
privateData.setProperty ("Bypass", var (isBypassed()), nullptr);
privateData.writeToStream (out);
}
void setJucePrivateStateInformation (const void* data, int sizeInBytes)
{
ValueTree privateData = ValueTree::readFromData (data, static_cast<size_t> (sizeInBytes));
setBypassed (static_cast<bool> (privateData.getProperty ("Bypass", var (false))));
}
void getStateInformation (MemoryBlock& destData)
{
pluginInstance->getStateInformation (destData);
// With bypass support, JUCE now needs to store private state data.
// Put this at the end of the plug-in state and add a few null characters
// so that plug-ins built with older versions of JUCE will hopefully ignore
// this data. Additionally, we need to add some sort of magic identifier
// at the very end of the private data so that JUCE has some sort of
// way to figure out if the data was stored with a newer JUCE version.
MemoryOutputStream extraData;
extraData.writeInt64 (0);
writeJucePrivateStateInformation (extraData);
const int64 privateDataSize = (int64) (extraData.getDataSize() - sizeof (int64));
extraData.writeInt64 (privateDataSize);
extraData << kJucePrivateDataIdentifier;
// write magic string
destData.append (extraData.getData(), extraData.getDataSize());
}
void setStateInformation (const void* data, int sizeAsInt)
{
size_t size = static_cast<size_t> (sizeAsInt);
// Check if this data was written with a newer JUCE version
// and if it has the JUCE private data magic code at the end
const size_t jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier);
if (size >= (jucePrivDataIdentifierSize + sizeof (int64)))
{
const char* buffer = static_cast<const char*> (data);
String magic (CharPointer_UTF8 (buffer + size - jucePrivDataIdentifierSize),
CharPointer_UTF8 (buffer + size));
if (magic == kJucePrivateDataIdentifier)
{
// found a JUCE private data section
uint64 privateDataSize;
std::memcpy (&privateDataSize,
buffer + (size - jucePrivDataIdentifierSize - sizeof (uint64)),
sizeof (uint64));
privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize);
size -= privateDataSize + jucePrivDataIdentifierSize + sizeof (uint64);
if (privateDataSize > 0)
setJucePrivateStateInformation (buffer + size, static_cast<int> (privateDataSize));
size -= sizeof (uint64);
}
}
pluginInstance->setStateInformation (data, static_cast<int> (size));
}
//==============================================================================
#if JUCE_VST3_CAN_REPLACE_VST2 #if JUCE_VST3_CAN_REPLACE_VST2
void loadVST2VstWBlock (const char* data, int size) void loadVST2VstWBlock (const char* data, int size)
{ {
const int headerLen = htonl (*(juce::int32*) (data + 4));
const int headerLen = static_cast<int> (htonl (*(juce::int32*) (data + 4)));
const struct fxBank* bank = (const struct fxBank*) (data + (8 + headerLen)); const struct fxBank* bank = (const struct fxBank*) (data + (8 + headerLen));
const int version = htonl (bank->version); (void) version;
const int version = static_cast<int> (htonl (bank->version)); (void) version;
jassert ('VstW' == htonl (*(juce::int32*) data)); jassert ('VstW' == htonl (*(juce::int32*) data));
jassert (1 == htonl (*(juce::int32*) (data + 8))); // version should be 1 according to Steinberg's docs jassert (1 == htonl (*(juce::int32*) (data + 8))); // version should be 1 according to Steinberg's docs
@@ -866,9 +1048,9 @@ public:
jassert (version == 1 || version == 2); jassert (version == 1 || version == 2);
jassert (JucePlugin_VSTUniqueID == htonl (bank->fxID)); jassert (JucePlugin_VSTUniqueID == htonl (bank->fxID));
pluginInstance->setStateInformation (bank->content.data.chunk,
jmin ((int) (size - (bank->content.data.chunk - data)),
(int) htonl (bank->content.data.size)));
setStateInformation (bank->content.data.chunk,
jmin ((int) (size - (bank->content.data.chunk - data)),
(int) htonl (bank->content.data.size)));
} }
bool loadVST3PresetFile (const char* data, int size) bool loadVST3PresetFile (const char* data, int size)
@@ -896,7 +1078,7 @@ public:
juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4);
juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12);
if (chunkOffset + chunkSize > size)
if (chunkOffset + chunkSize > static_cast<juce::uint64> (size))
{ {
jassertfalse; jassertfalse;
return false; return false;
@@ -938,7 +1120,7 @@ public:
#if JUCE_VST3_CAN_REPLACE_VST2 #if JUCE_VST3_CAN_REPLACE_VST2
return loadVST2CompatibleState ((const char*) data, size); return loadVST2CompatibleState ((const char*) data, size);
#else #else
pluginInstance->setStateInformation (data, size);
setStateInformation (data, size);
return true; return true;
#endif #endif
} }
@@ -979,7 +1161,7 @@ public:
if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab())) if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab()))
break; break;
allData.write (buffer, bytesRead);
allData.write (buffer, static_cast<size_t> (bytesRead));
} }
} }
@@ -1010,13 +1192,13 @@ public:
return state->write (&t, 4); return state->write (&t, 4);
} }
static tresult writeVST2Header (IBStream* state)
static tresult writeVST2Header (IBStream* state, bool bypassed)
{ {
tresult status = writeVST2Int (state, 'VstW'); tresult status = writeVST2Int (state, 'VstW');
if (status == kResultOk) status = writeVST2Int (state, 8); // header size if (status == kResultOk) status = writeVST2Int (state, 8); // header size
if (status == kResultOk) status = writeVST2Int (state, 1); // version if (status == kResultOk) status = writeVST2Int (state, 1); // version
if (status == kResultOk) status = writeVST2Int (state, 0); // bypass
if (status == kResultOk) status = writeVST2Int (state, bypassed ? 1 : 0); // bypass
return status; return status;
} }
@@ -1028,10 +1210,10 @@ public:
return kInvalidArgument; return kInvalidArgument;
juce::MemoryBlock mem; juce::MemoryBlock mem;
pluginInstance->getStateInformation (mem);
getStateInformation (mem);
#if JUCE_VST3_CAN_REPLACE_VST2 #if JUCE_VST3_CAN_REPLACE_VST2
tresult status = writeVST2Header (state);
tresult status = writeVST2Header (state, isBypassed());
if (status != kResultOk) if (status != kResultOk)
return status; return status;
@@ -1040,13 +1222,13 @@ public:
struct fxBank bank; struct fxBank bank;
zerostruct (bank); zerostruct (bank);
bank.chunkMagic = htonl (cMagic);
bank.byteSize = htonl (bankBlockSize - 8 + (unsigned int) mem.getSize());
bank.fxMagic = htonl (chunkBankMagic);
bank.version = htonl (2);
bank.fxID = htonl (JucePlugin_VSTUniqueID);
bank.fxVersion = htonl (JucePlugin_VersionCode);
bank.content.data.size = htonl ((unsigned int) mem.getSize());
bank.chunkMagic = (VstInt32) htonl (cMagic);
bank.byteSize = (VstInt32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize());
bank.fxMagic = (VstInt32) htonl (chunkBankMagic);
bank.version = (VstInt32) htonl (2);
bank.fxID = (VstInt32) htonl (JucePlugin_VSTUniqueID);
bank.fxVersion = (VstInt32) htonl (JucePlugin_VersionCode);
bank.content.data.size = (VstInt32) htonl ((unsigned int) mem.getSize());
status = state->write (&bank, bankBlockSize); status = state->write (&bank, bankBlockSize);
@@ -1304,6 +1486,8 @@ public:
if (isPositiveAndBelow (id, pluginInstance->getNumParameters())) if (isPositiveAndBelow (id, pluginInstance->getNumParameters()))
pluginInstance->setParameter (id, (float) value); pluginInstance->setParameter (id, (float) value);
else if (id == vstBypassParameterId)
setBypassed (static_cast<float> (value) != 0.0f);
else else
addParameterChangeToMidiBuffer (offsetSamples, id, value); addParameterChangeToMidiBuffer (offsetSamples, id, value);
} }
@@ -1386,9 +1570,16 @@ public:
processParameterChanges (*data.inputParameterChanges); processParameterChanges (*data.inputParameterChanges);
if (pluginInstance->isSuspended()) if (pluginInstance->isSuspended())
{
buffer.clear(); buffer.clear();
}
else else
pluginInstance->processBlock (buffer, midiBuffer);
{
if (isBypassed())
pluginInstance->processBlockBypassed (buffer, midiBuffer);
else
pluginInstance->processBlock (buffer, midiBuffer);
}
} }
for (int i = 0; i < numOutputChans; ++i) for (int i = 0; i < numOutputChans; ++i)
@@ -1446,6 +1637,10 @@ private:
ScopedJuceInitialiser_GUI libraryInitialiser; ScopedJuceInitialiser_GUI libraryInitialiser;
int vstBypassParameterId;
static const char* kJucePrivateDataIdentifier;
//============================================================================== //==============================================================================
void addBusTo (Vst::BusList& busList, Vst::Bus* newBus) void addBusTo (Vst::BusList& busList, Vst::Bus* newBus)
{ {
@@ -1505,6 +1700,8 @@ private:
Steinberg::FUID getJuceVST3ComponentIID() { return JuceVST3Component::iid; } Steinberg::FUID getJuceVST3ComponentIID() { return JuceVST3Component::iid; }
#endif #endif
const char* JuceVST3Component::kJucePrivateDataIdentifier = "JUCEPrivateData";
//============================================================================== //==============================================================================
#if JUCE_MSVC #if JUCE_MSVC
#pragma warning (push, 0) #pragma warning (push, 0)
@@ -1537,7 +1734,7 @@ DEF_CLASS_IID (JuceAudioProcessor)
for (size_t i = 0; i <= 8; ++i) for (size_t i = 0; i <= 8; ++i)
{ {
juce::uint8 c = i < len ? JucePlugin_Name[i] : 0;
juce::uint8 c = i < len ? static_cast<juce::uint8> (JucePlugin_Name[i]) : 0;
if (c >= 'A' && c <= 'Z') if (c >= 'A' && c <= 'Z')
c += 'a' - 'A'; c += 'a' - 'A';


+ 5
- 0
modules/juce_audio_processors/format_types/juce_VST3Common.h View File

@@ -67,6 +67,11 @@ static juce::String toString (const Steinberg::char16* string) noexcept { re
static juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } static juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); }
static juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } static juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); }
static void toString128 (Steinberg::Vst::String128 result, const char* source)
{
Steinberg::UString (result, 128).fromAscii (source);
}
static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) static void toString128 (Steinberg::Vst::String128 result, const juce::String& source)
{ {
Steinberg::UString (result, 128).fromAscii (source.toUTF8()); Steinberg::UString (result, 128).fromAscii (source.toUTF8());


Loading…
Cancel
Save