|
|
@@ -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';
|
|
|
|