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:
JuceAudioProcessor (AudioProcessor* source) noexcept
: refCount (0), audioProcessor (source) {}
: isBypassed (false), refCount (0), audioProcessor (source) {}
virtual ~JuceAudioProcessor() {}
@@ -84,6 +84,8 @@ public:
static const FUID iid;
bool isBypassed;
private:
Atomic<int> refCount;
ScopedPointer<AudioProcessor> audioProcessor;
@@ -230,14 +232,104 @@ public:
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
{
// Cubase and Nuendo need to inform the host of the current parameter values
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) numParameters, audioProcessor->isBypassed ? 1.0f : 0.0f);
}
return Vst::EditController::setComponentState (stream);
@@ -274,7 +366,7 @@ public:
tresult PLUGIN_API getMidiControllerAssignment (Steinberg::int32 /*busIndex*/, Steinberg::int16 channel,
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
}
@@ -366,17 +458,25 @@ private:
pluginInstance->addListener (this);
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));
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);
}
}
void initialiseMidiControllerMappings (const int numParameters)
void initialiseMidiControllerMappings (const int numVstParameters)
{
parameterToMidiControllerOffset = numParameters;
parameterToMidiControllerOffset = numVstParameters;
for (int c = 0, p = 0; c < numMIDIChannels; ++c)
{
@@ -387,7 +487,7 @@ private:
parameterToMidiController[p].ctrlNumber = 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));
}
}
@@ -655,6 +755,8 @@ public:
processSetup.sampleRate = 44100.0;
processSetup.symbolicSampleSize = Vst::kSample32;
vstBypassParameterId = pluginInstance->getNumParameters();
pluginInstance->setPlayHead (this);
}
@@ -852,12 +954,92 @@ public:
tresult PLUGIN_API setIoMode (Vst::IoMode) 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
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 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 (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 (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)
@@ -896,7 +1078,7 @@ public:
juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4);
juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12);
if (chunkOffset + chunkSize > size)
if (chunkOffset + chunkSize > static_cast<juce::uint64> (size))
{
jassertfalse;
return false;
@@ -938,7 +1120,7 @@ public:
#if JUCE_VST3_CAN_REPLACE_VST2
return loadVST2CompatibleState ((const char*) data, size);
#else
pluginInstance->setStateInformation (data, size);
setStateInformation (data, size);
return true;
#endif
}
@@ -979,7 +1161,7 @@ public:
if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab()))
break;
allData.write (buffer, bytesRead);
allData.write (buffer, static_cast<size_t> (bytesRead));
}
}
@@ -1010,13 +1192,13 @@ public:
return state->write (&t, 4);
}
static tresult writeVST2Header (IBStream* state)
static tresult writeVST2Header (IBStream* state, bool bypassed)
{
tresult status = writeVST2Int (state, 'VstW');
if (status == kResultOk) status = writeVST2Int (state, 8); // header size
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;
}
@@ -1028,10 +1210,10 @@ public:
return kInvalidArgument;
juce::MemoryBlock mem;
pluginInstance->getStateInformation (mem);
getStateInformation (mem);
#if JUCE_VST3_CAN_REPLACE_VST2
tresult status = writeVST2Header (state);
tresult status = writeVST2Header (state, isBypassed());
if (status != kResultOk)
return status;
@@ -1040,13 +1222,13 @@ public:
struct fxBank 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);
@@ -1304,6 +1486,8 @@ public:
if (isPositiveAndBelow (id, pluginInstance->getNumParameters()))
pluginInstance->setParameter (id, (float) value);
else if (id == vstBypassParameterId)
setBypassed (static_cast<float> (value) != 0.0f);
else
addParameterChangeToMidiBuffer (offsetSamples, id, value);
}
@@ -1386,9 +1570,16 @@ public:
processParameterChanges (*data.inputParameterChanges);
if (pluginInstance->isSuspended())
{
buffer.clear();
}
else
pluginInstance->processBlock (buffer, midiBuffer);
{
if (isBypassed())
pluginInstance->processBlockBypassed (buffer, midiBuffer);
else
pluginInstance->processBlock (buffer, midiBuffer);
}
}
for (int i = 0; i < numOutputChans; ++i)
@@ -1446,6 +1637,10 @@ private:
ScopedJuceInitialiser_GUI libraryInitialiser;
int vstBypassParameterId;
static const char* kJucePrivateDataIdentifier;
//==============================================================================
void addBusTo (Vst::BusList& busList, Vst::Bus* newBus)
{
@@ -1505,6 +1700,8 @@ private:
Steinberg::FUID getJuceVST3ComponentIID() { return JuceVST3Component::iid; }
#endif
const char* JuceVST3Component::kJucePrivateDataIdentifier = "JUCEPrivateData";
//==============================================================================
#if JUCE_MSVC
#pragma warning (push, 0)
@@ -1537,7 +1734,7 @@ DEF_CLASS_IID (JuceAudioProcessor)
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')
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::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)
{
Steinberg::UString (result, 128).fromAscii (source.toUTF8());


Loading…
Cancel
Save