@@ -313,7 +313,6 @@ endfunction() | |||||
function(_juce_add_plugin_wrapper_target format path out_path) | function(_juce_add_plugin_wrapper_target format path out_path) | ||||
_juce_module_sources("${path}" "${out_path}" out_var headers) | _juce_module_sources("${path}" "${out_path}" out_var headers) | ||||
list(FILTER out_var EXCLUDE REGEX "/juce_audio_plugin_client_utils.cpp$") | |||||
set(target_name juce_audio_plugin_client_${format}) | set(target_name juce_audio_plugin_client_${format}) | ||||
_juce_add_interface_library("${target_name}" ${out_var}) | _juce_add_interface_library("${target_name}" ${out_var}) | ||||
@@ -452,16 +451,6 @@ function(juce_add_module module_path) | |||||
_juce_add_plugin_wrapper_target(${kind} "${module_path}" "${base_path}") | _juce_add_plugin_wrapper_target(${kind} "${module_path}" "${base_path}") | ||||
endforeach() | endforeach() | ||||
set(utils_source | |||||
"${base_path}/${module_name}/juce_audio_plugin_client_utils.cpp") | |||||
add_library(juce_audio_plugin_client_utils INTERFACE) | |||||
target_sources(juce_audio_plugin_client_utils INTERFACE "${utils_source}") | |||||
if(JUCE_ARG_ALIAS_NAMESPACE) | |||||
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_audio_plugin_client_utils | |||||
ALIAS juce_audio_plugin_client_utils) | |||||
endif() | |||||
file(GLOB_RECURSE all_module_files | file(GLOB_RECURSE all_module_files | ||||
CONFIGURE_DEPENDS LIST_DIRECTORIES FALSE | CONFIGURE_DEPENDS LIST_DIRECTORIES FALSE | ||||
RELATIVE "${module_parent_path}" | RELATIVE "${module_parent_path}" | ||||
@@ -1211,7 +1211,7 @@ endfunction() | |||||
function(_juce_configure_plugin_targets target) | function(_juce_configure_plugin_targets target) | ||||
_juce_set_output_name(${target} $<TARGET_PROPERTY:${target},JUCE_PRODUCT_NAME>_SharedCode) | _juce_set_output_name(${target} $<TARGET_PROPERTY:${target},JUCE_PRODUCT_NAME>_SharedCode) | ||||
target_link_libraries(${target} PRIVATE juce::juce_audio_plugin_client_utils) | |||||
target_link_libraries(${target} PRIVATE juce::juce_audio_plugin_client) | |||||
get_target_property(enabled_formats ${target} JUCE_FORMATS) | get_target_property(enabled_formats ${target} JUCE_FORMATS) | ||||
@@ -1,45 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2022 - Raw Material Software Limited | |||||
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 7 End-User License | |||||
Agreement and JUCE Privacy Policy. | |||||
End User License Agreement: www.juce.com/juce-7-licence | |||||
Privacy Policy: www.juce.com/juce-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 <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h> | |||||
#if JucePlugin_Enable_ARA | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h> | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", "-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") | |||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) | |||||
#include <ARA_Library/PlugIn/ARAPlug.cpp> | |||||
#include <ARA_Library/Dispatch/ARAPlugInDispatch.cpp> | |||||
#include <ARA_Library/Utilities/ARAPitchInterpretation.cpp> | |||||
#include <ARA_Library/Utilities/ARAChannelArrangement.cpp> | |||||
JUCE_END_IGNORE_WARNINGS_MSVC | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif |
@@ -1,170 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2022 - Raw Material Software Limited | |||||
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 7 End-User License | |||||
Agreement and JUCE Privacy Policy. | |||||
End User License Agreement: www.juce.com/juce-7-licence | |||||
Privacy Policy: www.juce.com/juce-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 <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_WindowsHooks.h> | |||||
#include <juce_audio_devices/juce_audio_devices.h> | |||||
#include <juce_gui_extra/juce_gui_extra.h> | |||||
#include <juce_audio_utils/juce_audio_utils.h> | |||||
// You can set this flag in your build if you need to specify a different | |||||
// standalone JUCEApplication class for your app to use. If you don't | |||||
// set it then by default we'll just create a simple one as below. | |||||
#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | |||||
#include <juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h> | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
class StandaloneFilterApp : public JUCEApplication | |||||
{ | |||||
public: | |||||
StandaloneFilterApp() | |||||
{ | |||||
PropertiesFile::Options options; | |||||
options.applicationName = getApplicationName(); | |||||
options.filenameSuffix = ".settings"; | |||||
options.osxLibrarySubFolder = "Application Support"; | |||||
#if JUCE_LINUX || JUCE_BSD | |||||
options.folderName = "~/.config"; | |||||
#else | |||||
options.folderName = ""; | |||||
#endif | |||||
appProperties.setStorageParameters (options); | |||||
} | |||||
const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } | |||||
const String getApplicationVersion() override { return JucePlugin_VersionString; } | |||||
bool moreThanOneInstanceAllowed() override { return true; } | |||||
void anotherInstanceStarted (const String&) override {} | |||||
virtual StandaloneFilterWindow* createWindow() | |||||
{ | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; | |||||
#endif | |||||
return new StandaloneFilterWindow (getApplicationName(), | |||||
LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), | |||||
appProperties.getUserSettings(), | |||||
false, {}, nullptr | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
, juce::Array<StandalonePluginHolder::PluginInOuts> (channels, juce::numElementsInArray (channels)) | |||||
#else | |||||
, {} | |||||
#endif | |||||
#if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE | |||||
, false | |||||
#endif | |||||
); | |||||
} | |||||
//============================================================================== | |||||
void initialise (const String&) override | |||||
{ | |||||
mainWindow.reset (createWindow()); | |||||
#if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE | |||||
Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); | |||||
#endif | |||||
mainWindow->setVisible (true); | |||||
} | |||||
void shutdown() override | |||||
{ | |||||
mainWindow = nullptr; | |||||
appProperties.saveIfNeeded(); | |||||
} | |||||
//============================================================================== | |||||
void systemRequestedQuit() override | |||||
{ | |||||
if (mainWindow.get() != nullptr) | |||||
mainWindow->pluginHolder->savePluginState(); | |||||
if (ModalComponentManager::getInstance()->cancelAllModalComponents()) | |||||
{ | |||||
Timer::callAfterDelay (100, []() | |||||
{ | |||||
if (auto app = JUCEApplicationBase::getInstance()) | |||||
app->systemRequestedQuit(); | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
quit(); | |||||
} | |||||
} | |||||
protected: | |||||
ApplicationProperties appProperties; | |||||
std::unique_ptr<StandaloneFilterWindow> mainWindow; | |||||
}; | |||||
} // namespace juce | |||||
#if JucePlugin_Build_Standalone && JUCE_IOS | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") | |||||
using namespace juce; | |||||
bool JUCE_CALLTYPE juce_isInterAppAudioConnected() | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
return holder->isInterAppAudioConnected(); | |||||
return false; | |||||
} | |||||
void JUCE_CALLTYPE juce_switchToHostApplication() | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
holder->switchToHostApplication(); | |||||
} | |||||
Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
return holder->getIAAHostIcon (size); | |||||
return Image(); | |||||
} | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif | |||||
#endif |
@@ -1,774 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2022 - Raw Material Software Limited | |||||
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 7 End-User License | |||||
Agreement and JUCE Privacy Policy. | |||||
End User License Agreement: www.juce.com/juce-7-licence | |||||
Privacy Policy: www.juce.com/juce-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> | |||||
#if JucePlugin_Build_Unity | |||||
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h> | |||||
#include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | |||||
#if JUCE_WINDOWS | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#endif | |||||
#include "juce_UnityPluginInterface.h" | |||||
//============================================================================== | |||||
namespace juce | |||||
{ | |||||
typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); | |||||
extern createUnityPeerFunctionType juce_createUnityPeerFn; | |||||
//============================================================================== | |||||
class UnityPeer : public ComponentPeer, | |||||
public AsyncUpdater | |||||
{ | |||||
public: | |||||
UnityPeer (Component& ed) | |||||
: ComponentPeer (ed, 0), | |||||
mouseWatcher (*this) | |||||
{ | |||||
getEditor().setResizable (false, false); | |||||
} | |||||
//============================================================================== | |||||
Rectangle<int> getBounds() const override { return bounds; } | |||||
Point<float> localToGlobal (Point<float> relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } | |||||
Point<float> globalToLocal (Point<float> screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } | |||||
using ComponentPeer::localToGlobal; | |||||
using ComponentPeer::globalToLocal; | |||||
StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } | |||||
void setBounds (const Rectangle<int>& newBounds, bool) override | |||||
{ | |||||
bounds = newBounds; | |||||
mouseWatcher.setBoundsToWatch (bounds); | |||||
} | |||||
bool contains (Point<int> localPos, bool) const override | |||||
{ | |||||
if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) | |||||
&& isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) | |||||
return true; | |||||
return false; | |||||
} | |||||
void handleAsyncUpdate() override | |||||
{ | |||||
fillPixels(); | |||||
} | |||||
//============================================================================== | |||||
AudioProcessorEditor& getEditor() { return *dynamic_cast<AudioProcessorEditor*> (&getComponent()); } | |||||
void setPixelDataHandle (uint8* handle, int width, int height) | |||||
{ | |||||
pixelData = handle; | |||||
textureWidth = width; | |||||
textureHeight = height; | |||||
renderImage = Image (new UnityBitmapImage (pixelData, width, height)); | |||||
} | |||||
// N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. | |||||
// It works for our purposes here but a much more efficient way would be to use a GL texture. | |||||
void fillPixels() | |||||
{ | |||||
if (pixelData == nullptr) | |||||
return; | |||||
LowLevelGraphicsSoftwareRenderer renderer (renderImage); | |||||
renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); | |||||
handlePaint (renderer); | |||||
for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) | |||||
{ | |||||
auto r = pixelData[i + 2]; | |||||
auto g = pixelData[i + 1]; | |||||
auto b = pixelData[i + 0]; | |||||
pixelData[i + 0] = r; | |||||
pixelData[i + 1] = g; | |||||
pixelData[i + 2] = b; | |||||
} | |||||
} | |||||
void forwardMouseEvent (Point<float> position, ModifierKeys mods) | |||||
{ | |||||
ModifierKeys::currentModifiers = mods; | |||||
handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, | |||||
juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); | |||||
} | |||||
void forwardKeyPress (int code, String name, ModifierKeys mods) | |||||
{ | |||||
ModifierKeys::currentModifiers = mods; | |||||
handleKeyPress (getKeyPress (code, name)); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
struct UnityBitmapImage : public ImagePixelData | |||||
{ | |||||
UnityBitmapImage (uint8* data, int w, int h) | |||||
: ImagePixelData (Image::PixelFormat::ARGB, w, h), | |||||
imageData (data), | |||||
lineStride (width * pixelStride) | |||||
{ | |||||
} | |||||
std::unique_ptr<ImageType> createType() const override | |||||
{ | |||||
return std::make_unique<SoftwareImageType>(); | |||||
} | |||||
std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override | |||||
{ | |||||
return std::make_unique<LowLevelGraphicsSoftwareRenderer> (Image (this)); | |||||
} | |||||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override | |||||
{ | |||||
const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; | |||||
bitmap.data = imageData + offset; | |||||
bitmap.size = (size_t) (lineStride * height) - offset; | |||||
bitmap.pixelFormat = pixelFormat; | |||||
bitmap.lineStride = lineStride; | |||||
bitmap.pixelStride = pixelStride; | |||||
} | |||||
ImagePixelData::Ptr clone() override | |||||
{ | |||||
auto im = new UnityBitmapImage (imageData, width, height); | |||||
for (int i = 0; i < height; ++i) | |||||
memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); | |||||
return im; | |||||
} | |||||
uint8* imageData; | |||||
int pixelStride = 4, lineStride; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) | |||||
}; | |||||
//============================================================================== | |||||
struct MouseWatcher : public Timer | |||||
{ | |||||
MouseWatcher (ComponentPeer& o) : owner (o) {} | |||||
void timerCallback() override | |||||
{ | |||||
auto pos = Desktop::getMousePosition(); | |||||
if (boundsToWatch.contains (pos) && pos != lastMousePos) | |||||
{ | |||||
auto ms = Desktop::getInstance().getMainMouseSource(); | |||||
if (! ms.getCurrentModifiers().isLeftButtonDown()) | |||||
owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, | |||||
juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); | |||||
lastMousePos = pos; | |||||
} | |||||
} | |||||
void setBoundsToWatch (Rectangle<int> b) | |||||
{ | |||||
if (boundsToWatch != b) | |||||
boundsToWatch = b; | |||||
startTimer (250); | |||||
} | |||||
ComponentPeer& owner; | |||||
Rectangle<int> boundsToWatch; | |||||
Point<int> lastMousePos; | |||||
}; | |||||
//============================================================================== | |||||
KeyPress getKeyPress (int keyCode, String name) | |||||
{ | |||||
if (keyCode >= 32 && keyCode <= 64) | |||||
return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; | |||||
if (keyCode >= 91 && keyCode <= 122) | |||||
return { keyCode, ModifierKeys::currentModifiers, name[0] }; | |||||
if (keyCode >= 256 && keyCode <= 265) | |||||
return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; | |||||
if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; | |||||
return {}; | |||||
} | |||||
//============================================================================== | |||||
Rectangle<int> bounds; | |||||
MouseWatcher mouseWatcher; | |||||
uint8* pixelData = nullptr; | |||||
int textureWidth, textureHeight; | |||||
Image renderImage; | |||||
//============================================================================== | |||||
void setMinimised (bool) override {} | |||||
bool isMinimised() const override { return false; } | |||||
void setFullScreen (bool) override {} | |||||
bool isFullScreen() const override { return false; } | |||||
bool setAlwaysOnTop (bool) override { return false; } | |||||
void toFront (bool) override {} | |||||
void toBehind (ComponentPeer*) override {} | |||||
bool isFocused() const override { return true; } | |||||
void grabFocus() override {} | |||||
void* getNativeHandle() const override { return nullptr; } | |||||
OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } | |||||
BorderSize<int> getFrameSize() const override { return {}; } | |||||
void setVisible (bool) override {} | |||||
void setTitle (const String&) override {} | |||||
void setIcon (const Image&) override {} | |||||
void textInputRequired (Point<int>, TextInputTarget&) override {} | |||||
void setAlpha (float) override {} | |||||
void performAnyPendingRepaintsNow() override {} | |||||
void repaint (const Rectangle<int>&) override {} | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) | |||||
}; | |||||
static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } | |||||
//============================================================================== | |||||
class AudioProcessorUnityWrapper | |||||
{ | |||||
public: | |||||
AudioProcessorUnityWrapper (bool isTemporary) | |||||
{ | |||||
pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); | |||||
if (! isTemporary && pluginInstance->hasEditor()) | |||||
{ | |||||
pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); | |||||
pluginInstanceEditor->setVisible (true); | |||||
detail::PluginUtilities::addToDesktop (*pluginInstanceEditor, nullptr); | |||||
} | |||||
juceParameters.update (*pluginInstance, false); | |||||
} | |||||
~AudioProcessorUnityWrapper() | |||||
{ | |||||
if (pluginInstanceEditor != nullptr) | |||||
{ | |||||
pluginInstanceEditor->removeFromDesktop(); | |||||
PopupMenu::dismissAllActiveMenus(); | |||||
pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); | |||||
pluginInstanceEditor = nullptr; | |||||
} | |||||
} | |||||
void create (UnityAudioEffectState* state) | |||||
{ | |||||
// only supported in Unity plugin API > 1.0 | |||||
if (state->structSize >= sizeof (UnityAudioEffectState)) | |||||
samplesPerBlock = static_cast<int> (state->dspBufferSize); | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||||
const int numConfigs = sizeof (configs) / sizeof (short[2]); | |||||
jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); | |||||
pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); | |||||
#else | |||||
pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); | |||||
#endif | |||||
pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); | |||||
scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); | |||||
} | |||||
void release() | |||||
{ | |||||
pluginInstance->releaseResources(); | |||||
} | |||||
void reset() | |||||
{ | |||||
pluginInstance->reset(); | |||||
} | |||||
void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) | |||||
{ | |||||
// If the plugin has a bypass parameter, set it to the current bypass state | |||||
if (auto* param = pluginInstance->getBypassParameter()) | |||||
if (isBypassed != (param->getValue() >= 0.5f)) | |||||
param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); | |||||
for (int pos = 0; pos < bufferSize;) | |||||
{ | |||||
auto max = jmin (bufferSize - pos, samplesPerBlock); | |||||
processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); | |||||
pos += max; | |||||
} | |||||
} | |||||
void declareParameters (UnityAudioEffectDefinition& definition) | |||||
{ | |||||
static std::unique_ptr<UnityAudioParameterDefinition> parametersPtr; | |||||
static int numParams = 0; | |||||
if (parametersPtr == nullptr) | |||||
{ | |||||
numParams = (int) juceParameters.size(); | |||||
parametersPtr.reset (static_cast<UnityAudioParameterDefinition*> (std::calloc (static_cast<size_t> (numParams), | |||||
sizeof (UnityAudioParameterDefinition)))); | |||||
parameterDescriptions.clear(); | |||||
for (int i = 0; i < numParams; ++i) | |||||
{ | |||||
auto* parameter = juceParameters.getParamForIndex (i); | |||||
auto& paramDef = parametersPtr.get()[i]; | |||||
const auto nameLength = (size_t) numElementsInArray (paramDef.name); | |||||
const auto unitLength = (size_t) numElementsInArray (paramDef.unit); | |||||
parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); | |||||
if (parameter->getLabel().isNotEmpty()) | |||||
parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); | |||||
parameterDescriptions.add (parameter->getName (15)); | |||||
paramDef.description = parameterDescriptions[i].toRawUTF8(); | |||||
paramDef.defaultVal = parameter->getDefaultValue(); | |||||
paramDef.min = 0.0f; | |||||
paramDef.max = 1.0f; | |||||
paramDef.displayScale = 1.0f; | |||||
paramDef.displayExponent = 1.0f; | |||||
} | |||||
} | |||||
definition.numParameters = static_cast<uint32> (numParams); | |||||
definition.parameterDefintions = parametersPtr.get(); | |||||
} | |||||
void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } | |||||
float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } | |||||
String getParameterString (int index) const noexcept | |||||
{ | |||||
auto* param = juceParameters.getParamForIndex (index); | |||||
return param->getText (param->getValue(), 16); | |||||
} | |||||
int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } | |||||
int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } | |||||
bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } | |||||
UnityPeer& getEditorPeer() const | |||||
{ | |||||
auto* peer = dynamic_cast<UnityPeer*> (pluginInstanceEditor->getPeer()); | |||||
jassert (peer != nullptr); | |||||
return *peer; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) | |||||
{ | |||||
int ch; | |||||
for (ch = 0; ch < numInChannels; ++ch) | |||||
{ | |||||
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>; | |||||
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const>; | |||||
DstSampleType dstData (scratchBuffer.getWritePointer (ch)); | |||||
SrcSampleType srcData (inBuffer + ch, numInChannels); | |||||
dstData.convertSamples (srcData, bufferSize); | |||||
} | |||||
for (; ch < numOutChannels; ++ch) | |||||
scratchBuffer.clear (ch, 0, bufferSize); | |||||
{ | |||||
const ScopedLock sl (pluginInstance->getCallbackLock()); | |||||
if (pluginInstance->isSuspended()) | |||||
{ | |||||
scratchBuffer.clear(); | |||||
} | |||||
else | |||||
{ | |||||
MidiBuffer mb; | |||||
if (isBypassed && pluginInstance->getBypassParameter() == nullptr) | |||||
pluginInstance->processBlockBypassed (scratchBuffer, mb); | |||||
else | |||||
pluginInstance->processBlock (scratchBuffer, mb); | |||||
} | |||||
} | |||||
for (ch = 0; ch < numOutChannels; ++ch) | |||||
{ | |||||
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst>; | |||||
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>; | |||||
DstSampleType dstData (outBuffer + ch, numOutChannels); | |||||
SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); | |||||
dstData.convertSamples (srcData, bufferSize); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
std::unique_ptr<AudioProcessor> pluginInstance; | |||||
std::unique_ptr<AudioProcessorEditor> pluginInstanceEditor; | |||||
int samplesPerBlock = 1024; | |||||
StringArray parameterDescriptions; | |||||
AudioBuffer<float> scratchBuffer; | |||||
LegacyAudioParametersWrapper juceParameters; | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) | |||||
}; | |||||
//============================================================================== | |||||
static HashMap<int, AudioProcessorUnityWrapper*>& getWrapperMap() | |||||
{ | |||||
static HashMap<int, AudioProcessorUnityWrapper*> wrapperMap; | |||||
return wrapperMap; | |||||
} | |||||
static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) | |||||
{ | |||||
getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); | |||||
} | |||||
static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) | |||||
{ | |||||
getWrapperMap().removeValue (wrapperToRemove); | |||||
} | |||||
//============================================================================== | |||||
static UnityAudioEffectDefinition getEffectDefinition() | |||||
{ | |||||
const auto wrapper = std::make_unique<AudioProcessorUnityWrapper> (true); | |||||
const String originalName { JucePlugin_Name }; | |||||
const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; | |||||
UnityAudioEffectDefinition result{}; | |||||
name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); | |||||
result.structSize = sizeof (UnityAudioEffectDefinition); | |||||
result.parameterStructSize = sizeof (UnityAudioParameterDefinition); | |||||
result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; | |||||
result.pluginVersion = JucePlugin_VersionCode; | |||||
// effects must set this to 0, generators > 0 | |||||
result.channels = (wrapper->getNumInputChannels() != 0 ? 0 | |||||
: static_cast<uint32> (wrapper->getNumOutputChannels())); | |||||
wrapper->declareParameters (result); | |||||
result.create = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = new AudioProcessorUnityWrapper (false); | |||||
pluginInstance->create (state); | |||||
state->effectData = pluginInstance; | |||||
onWrapperCreation (pluginInstance); | |||||
return 0; | |||||
}; | |||||
result.release = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->release(); | |||||
onWrapperDeletion (pluginInstance); | |||||
delete pluginInstance; | |||||
if (getWrapperMap().size() == 0) | |||||
shutdownJuce_GUI(); | |||||
return 0; | |||||
}; | |||||
result.reset = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->reset(); | |||||
return 0; | |||||
}; | |||||
result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) | |||||
{ | |||||
ignoreUnused (state, pos); | |||||
return 0; | |||||
}; | |||||
result.process = [] (UnityAudioEffectState* state, | |||||
float* inBuffer, | |||||
float* outBuffer, | |||||
unsigned int bufferSize, | |||||
int numInChannels, | |||||
int numOutChannels) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
if (pluginInstance != nullptr) | |||||
{ | |||||
auto isPlaying = ((state->flags & stateIsPlaying) != 0); | |||||
auto isMuted = ((state->flags & stateIsMuted) != 0); | |||||
auto isPaused = ((state->flags & stateIsPaused) != 0); | |||||
const auto bypassed = ! isPlaying || (isMuted || isPaused); | |||||
pluginInstance->process (inBuffer, outBuffer, static_cast<int> (bufferSize), numInChannels, numOutChannels, bypassed); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::clear (outBuffer, static_cast<int> (bufferSize) * numOutChannels); | |||||
} | |||||
return 0; | |||||
}; | |||||
result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->setParameter (index, value); | |||||
return 0; | |||||
}; | |||||
result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
*value = pluginInstance->getParameter (index); | |||||
pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); | |||||
return 0; | |||||
}; | |||||
result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) | |||||
{ | |||||
ignoreUnused (numSamples); | |||||
const StringRef kindStr { kind }; | |||||
if (kindStr == StringRef ("Editor")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; | |||||
} | |||||
else if (kindStr == StringRef ("ID")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
for (HashMap<int, AudioProcessorUnityWrapper*>::Iterator i (getWrapperMap()); i.next();) | |||||
{ | |||||
if (i.getValue() == pluginInstance) | |||||
{ | |||||
buffer[0] = (float) i.getKey(); | |||||
break; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
else if (kindStr == StringRef ("Size")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
auto& editor = pluginInstance->getEditorPeer().getEditor(); | |||||
buffer[0] = (float) editor.getBounds().getWidth(); | |||||
buffer[1] = (float) editor.getBounds().getHeight(); | |||||
buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); | |||||
buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); | |||||
buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); | |||||
buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); | |||||
} | |||||
return 0; | |||||
}; | |||||
return result; | |||||
} | |||||
} // namespace juce | |||||
// From reading the example code, it seems that the triple indirection indicates | |||||
// an out-value of an array of pointers. That is, after calling this function, definitionsPtr | |||||
// should point to a pre-existing/static array of pointer-to-effect-definition. | |||||
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) | |||||
{ | |||||
if (juce::getWrapperMap().size() == 0) | |||||
juce::initialiseJuce_GUI(); | |||||
static std::once_flag flag; | |||||
std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); | |||||
static auto definition = juce::getEffectDefinition(); | |||||
static UnityAudioEffectDefinition* definitions[] { &definition }; | |||||
*definitionsPtr = definitions; | |||||
return 1; | |||||
} | |||||
//============================================================================== | |||||
static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) | |||||
{ | |||||
int flags = 0; | |||||
if (mouseDown) | |||||
{ | |||||
if (mouseButton == 0) | |||||
flags |= juce::ModifierKeys::leftButtonModifier; | |||||
else if (mouseButton == 1) | |||||
flags |= juce::ModifierKeys::rightButtonModifier; | |||||
else if (mouseButton == 2) | |||||
flags |= juce::ModifierKeys::middleButtonModifier; | |||||
} | |||||
if (mods == 0) | |||||
return flags; | |||||
if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; | |||||
if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; | |||||
if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; | |||||
if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; | |||||
return { flags }; | |||||
} | |||||
//============================================================================== | |||||
static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) | |||||
{ | |||||
auto* wrapper = juce::getWrapperMap()[id]; | |||||
jassert (wrapper != nullptr); | |||||
return wrapper; | |||||
} | |||||
//============================================================================== | |||||
static void UNITY_INTERFACE_API onRenderEvent (int id) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); | |||||
} | |||||
UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() | |||||
{ | |||||
return onRenderEvent; | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast<juce::uint8*> (data), w, h); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_WINDOWS | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") | |||||
extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) | |||||
{ | |||||
if (reason == DLL_PROCESS_ATTACH) | |||||
juce::Process::setCurrentModuleInstanceHandle (instance); | |||||
return true; | |||||
} | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif | |||||
#endif |
@@ -1,145 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2022 - Raw Material Software Limited | |||||
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 7 End-User License | |||||
Agreement and JUCE Privacy Policy. | |||||
End User License Agreement: www.juce.com/juce-7-licence | |||||
Privacy Policy: www.juce.com/juce-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_audio_plugin_client/detail/juce_PluginUtilities.h> | |||||
#if JUCE_WINDOWS | |||||
#include <windows.h> | |||||
#endif | |||||
namespace juce::detail | |||||
{ | |||||
#if JucePlugin_Build_Unity | |||||
bool isRunningInUnity(); | |||||
bool isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } | |||||
#endif | |||||
#if VST3_REPLACEMENT_AVAILABLE | |||||
// NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. | |||||
void PluginUtilities::getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) | |||||
{ | |||||
#if JUCE_MSVC | |||||
const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, numElementsInArray (head), tail...); }; | |||||
const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, numElementsInArray (head), tail...); }; | |||||
const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, numElementsInArray (head), tail...); }; | |||||
const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; | |||||
#else | |||||
const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; | |||||
const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; | |||||
const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; | |||||
const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; | |||||
#endif | |||||
char uidString[33]; | |||||
const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); | |||||
char vstfxidStr[7] = { 0 }; | |||||
juce_sprintf (vstfxidStr, "%06X", vstfxid); | |||||
juce_strcpy (uidString, vstfxidStr); | |||||
char uidStr[9] = { 0 }; | |||||
juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); | |||||
juce_strcat (uidString, uidStr); | |||||
char nameidStr[3] = { 0 }; | |||||
const size_t len = strlen (JucePlugin_Name); | |||||
for (size_t i = 0; i <= 8; ++i) | |||||
{ | |||||
juce::uint8 c = i < len ? static_cast<juce::uint8> (JucePlugin_Name[i]) : 0; | |||||
if (c >= 'A' && c <= 'Z') | |||||
c += 'a' - 'A'; | |||||
juce_sprintf (nameidStr, "%02X", c); | |||||
juce_strcat (uidString, nameidStr); | |||||
} | |||||
unsigned long p0; | |||||
unsigned int p1, p2; | |||||
unsigned int p3[8]; | |||||
juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", | |||||
&p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); | |||||
union q0_u { | |||||
uint32 word; | |||||
uint8 bytes[4]; | |||||
} q0; | |||||
union q1_u { | |||||
uint16 half; | |||||
uint8 bytes[2]; | |||||
} q1, q2; | |||||
q0.word = static_cast<uint32> (p0); | |||||
q1.half = static_cast<uint16> (p1); | |||||
q2.half = static_cast<uint16> (p2); | |||||
// VST3 doesn't use COM compatible UUIDs on non windows platforms | |||||
#if ! JUCE_WINDOWS | |||||
q0.word = ByteOrder::swap (q0.word); | |||||
q1.half = ByteOrder::swap (q1.half); | |||||
q2.half = ByteOrder::swap (q2.half); | |||||
#endif | |||||
for (int i = 0; i < 4; ++i) | |||||
uuid[i+0] = q0.bytes[i]; | |||||
for (int i = 0; i < 2; ++i) | |||||
uuid[i+4] = q1.bytes[i]; | |||||
for (int i = 0; i < 2; ++i) | |||||
uuid[i+6] = q2.bytes[i]; | |||||
for (int i = 0; i < 8; ++i) | |||||
uuid[i+8] = static_cast<uint8> (p3[i]); | |||||
} | |||||
#endif | |||||
#if JucePlugin_Build_VST | |||||
bool PluginUtilities::handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, | |||||
[[maybe_unused]] pointer_sized_int value, | |||||
[[maybe_unused]] void* ptr, | |||||
float) | |||||
{ | |||||
#if VST3_REPLACEMENT_AVAILABLE | |||||
if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) | |||||
&& value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) | |||||
{ | |||||
uint8 fuid[16]; | |||||
getUUIDForVST2ID (false, fuid); | |||||
::memcpy (ptr, fuid, 16); | |||||
return true; | |||||
} | |||||
#endif | |||||
return false; | |||||
} | |||||
#endif | |||||
} // namespace juce |
@@ -30,6 +30,13 @@ | |||||
namespace juce::detail | namespace juce::detail | ||||
{ | { | ||||
bool isRunningInUnity(); | |||||
#if JucePlugin_Build_Unity | |||||
bool isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } | |||||
#else | |||||
bool isRunningInUnity() { return false; } | |||||
#endif | |||||
struct PluginUtilities | struct PluginUtilities | ||||
{ | { | ||||
PluginUtilities() = delete; | PluginUtilities() = delete; | ||||
@@ -61,20 +68,106 @@ struct PluginUtilities | |||||
#define JUCE_VST3_CAN_REPLACE_VST2 1 | #define JUCE_VST3_CAN_REPLACE_VST2 1 | ||||
#endif | #endif | ||||
#if JucePlugin_Build_VST3 && JUCE_VST3_CAN_REPLACE_VST2 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD) | |||||
#define VST3_REPLACEMENT_AVAILABLE 1 | |||||
static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]); | |||||
#else | |||||
#define VST3_REPLACEMENT_AVAILABLE 0 | |||||
#endif | |||||
// NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. | |||||
static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) | |||||
{ | |||||
#if JUCE_WINDOWS && ! JUCE_MINGW | |||||
const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, (size_t) numElementsInArray (head), tail...); }; | |||||
const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, (size_t) numElementsInArray (head), tail...); }; | |||||
const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, (size_t) numElementsInArray (head), tail...); }; | |||||
const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; | |||||
#else | |||||
const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; | |||||
const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; | |||||
const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; | |||||
const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; | |||||
#endif | |||||
char uidString[33]; | |||||
const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); | |||||
char vstfxidStr[7] = { 0 }; | |||||
juce_sprintf (vstfxidStr, "%06X", vstfxid); | |||||
juce_strcpy (uidString, vstfxidStr); | |||||
char uidStr[9] = { 0 }; | |||||
juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); | |||||
juce_strcat (uidString, uidStr); | |||||
char nameidStr[3] = { 0 }; | |||||
const size_t len = strlen (JucePlugin_Name); | |||||
for (size_t i = 0; i <= 8; ++i) | |||||
{ | |||||
juce::uint8 c = i < len ? static_cast<juce::uint8> (JucePlugin_Name[i]) : 0; | |||||
if (c >= 'A' && c <= 'Z') | |||||
c += 'a' - 'A'; | |||||
juce_sprintf (nameidStr, "%02X", c); | |||||
juce_strcat (uidString, nameidStr); | |||||
} | |||||
unsigned long p0; | |||||
unsigned int p1, p2; | |||||
unsigned int p3[8]; | |||||
juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", | |||||
&p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); | |||||
union q0_u { | |||||
uint32 word; | |||||
uint8 bytes[4]; | |||||
} q0; | |||||
union q1_u { | |||||
uint16 half; | |||||
uint8 bytes[2]; | |||||
} q1, q2; | |||||
q0.word = static_cast<uint32> (p0); | |||||
q1.half = static_cast<uint16> (p1); | |||||
q2.half = static_cast<uint16> (p2); | |||||
// VST3 doesn't use COM compatible UUIDs on non windows platforms | |||||
#if ! JUCE_WINDOWS | |||||
q0.word = ByteOrder::swap (q0.word); | |||||
q1.half = ByteOrder::swap (q1.half); | |||||
q2.half = ByteOrder::swap (q2.half); | |||||
#endif | |||||
for (int i = 0; i < 4; ++i) | |||||
uuid[i+0] = q0.bytes[i]; | |||||
for (int i = 0; i < 2; ++i) | |||||
uuid[i+4] = q1.bytes[i]; | |||||
for (int i = 0; i < 2; ++i) | |||||
uuid[i+6] = q2.bytes[i]; | |||||
for (int i = 0; i < 8; ++i) | |||||
uuid[i+8] = static_cast<uint8> (p3[i]); | |||||
} | |||||
#if JucePlugin_Build_VST | #if JucePlugin_Build_VST | ||||
static bool handleManufacturerSpecificVST2Opcode (int32 index, | |||||
pointer_sized_int value, | |||||
void* ptr, | |||||
float); | |||||
static bool handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, | |||||
[[maybe_unused]] pointer_sized_int value, | |||||
[[maybe_unused]] void* ptr, | |||||
float) | |||||
{ | |||||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||||
if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) | |||||
&& value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) | |||||
{ | |||||
uint8 fuid[16]; | |||||
getUUIDForVST2ID (false, fuid); | |||||
::memcpy (ptr, fuid, 16); | |||||
return true; | |||||
} | |||||
#endif | |||||
return false; | |||||
} | |||||
#endif | #endif | ||||
}; | }; | ||||
@@ -38,15 +38,59 @@ struct VSTWindowUtilities | |||||
static void* attachComponentToWindowRefVST (Component* comp, | static void* attachComponentToWindowRefVST (Component* comp, | ||||
int desktopFlags, | int desktopFlags, | ||||
void* parentWindowOrView); | |||||
void* parentWindowOrView) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
NSView* parentView = [(NSView*) parentWindowOrView retain]; | |||||
const auto defaultFlags = JucePlugin_EditorRequiresKeyboardFocus | |||||
? 0 | |||||
: ComponentPeer::windowIgnoresKeyPresses; | |||||
comp->addToDesktop (desktopFlags | defaultFlags, parentView); | |||||
// (this workaround is because Wavelab provides a zero-size parent view..) | |||||
if ([parentView frame].size.height == 0) | |||||
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; | |||||
comp->setVisible (true); | |||||
comp->toFront (false); | |||||
[[parentView window] setAcceptsMouseMovedEvents: YES]; | |||||
return parentView; | |||||
} | |||||
} | |||||
static void detachComponentFromWindowRefVST (Component* comp, | static void detachComponentFromWindowRefVST (Component* comp, | ||||
void* window); | |||||
void* window) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
comp->removeFromDesktop(); | |||||
[(id) window release]; | |||||
} | |||||
} | |||||
static void setNativeHostWindowSizeVST (void* window, | static void setNativeHostWindowSizeVST (void* window, | ||||
Component* component, | Component* component, | ||||
int newWidth, | int newWidth, | ||||
int newHeight); | |||||
int newHeight) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
if (NSView* hostView = (NSView*) window) | |||||
{ | |||||
const int dx = newWidth - component->getWidth(); | |||||
const int dy = newHeight - component->getHeight(); | |||||
NSRect r = [hostView frame]; | |||||
r.size.width += dx; | |||||
r.size.height += dy; | |||||
r.origin.y -= dy; | |||||
[hostView setFrame: r]; | |||||
} | |||||
} | |||||
} | |||||
}; | }; | ||||
} // namespace juce::detail | } // namespace juce::detail | ||||
@@ -1,96 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2022 - Raw Material Software Limited | |||||
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 7 End-User License | |||||
Agreement and JUCE Privacy Policy. | |||||
End User License Agreement: www.juce.com/juce-7-licence | |||||
Privacy Policy: www.juce.com/juce-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. | |||||
============================================================================== | |||||
*/ | |||||
#pragma once | |||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#if JUCE_MAC | |||||
#include <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h> | |||||
namespace juce::detail | |||||
{ | |||||
void* VSTWindowUtilities::attachComponentToWindowRefVST (Component* comp, | |||||
int desktopFlags, | |||||
void* parentWindowOrView) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
NSView* parentView = [(NSView*) parentWindowOrView retain]; | |||||
const auto defaultFlags = JucePlugin_EditorRequiresKeyboardFocus | |||||
? 0 | |||||
: ComponentPeer::windowIgnoresKeyPresses; | |||||
comp->addToDesktop (desktopFlags | defaultFlags, parentView); | |||||
// (this workaround is because Wavelab provides a zero-size parent view..) | |||||
if ([parentView frame].size.height == 0) | |||||
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; | |||||
comp->setVisible (true); | |||||
comp->toFront (false); | |||||
[[parentView window] setAcceptsMouseMovedEvents: YES]; | |||||
return parentView; | |||||
} | |||||
} | |||||
void VSTWindowUtilities::detachComponentFromWindowRefVST (Component* comp, void* window) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
comp->removeFromDesktop(); | |||||
[(id) window release]; | |||||
} | |||||
} | |||||
void VSTWindowUtilities::setNativeHostWindowSizeVST (void* window, | |||||
Component* component, | |||||
int newWidth, | |||||
int newHeight) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
if (NSView* hostView = (NSView*) window) | |||||
{ | |||||
const int dx = newWidth - component->getWidth(); | |||||
const int dy = newHeight - component->getHeight(); | |||||
NSRect r = [hostView frame]; | |||||
r.size.width += dx; | |||||
r.size.height += dy; | |||||
r.origin.y -= dy; | |||||
[hostView setFrame: r]; | |||||
} | |||||
} | |||||
} | |||||
} // namespace juce::detail | |||||
#endif |
@@ -24,4 +24,4 @@ | |||||
*/ | */ | ||||
#define JUCE_INCLUDED_AAX_IN_MM 1 | #define JUCE_INCLUDED_AAX_IN_MM 1 | ||||
#include "AAX/juce_AAX_Wrapper.cpp" | |||||
#include "juce_audio_plugin_client_AAX.cpp" |
@@ -25,7 +25,7 @@ | |||||
#include <juce_core/system/juce_TargetPlatform.h> | #include <juce_core/system/juce_TargetPlatform.h> | ||||
#if JucePlugin_Build_AAX && (JUCE_MAC || JUCE_WINDOWS) | |||||
#if JucePlugin_Build_AAX | |||||
#include <AAX_Version.h> | #include <AAX_Version.h> | ||||
@@ -23,4 +23,23 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include "ARA/juce_ARA_Wrapper.cpp" | |||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#include <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h> | |||||
#if JucePlugin_Enable_ARA | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h> | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", "-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") | |||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) | |||||
#include <ARA_Library/PlugIn/ARAPlug.cpp> | |||||
#include <ARA_Library/Dispatch/ARAPlugInDispatch.cpp> | |||||
#include <ARA_Library/Utilities/ARAPitchInterpretation.cpp> | |||||
#include <ARA_Library/Utilities/ARAChannelArrangement.cpp> | |||||
JUCE_END_IGNORE_WARNINGS_MSVC | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif |
@@ -86,18 +86,18 @@ enum MIDICVStatus : unsigned int | |||||
#endif | #endif | ||||
#include "AU/AudioUnitSDK/AUBase.cpp" | |||||
#include "AU/AudioUnitSDK/AUBuffer.cpp" | |||||
#include "AU/AudioUnitSDK/AUBufferAllocator.cpp" | |||||
#include "AU/AudioUnitSDK/AUEffectBase.cpp" | |||||
#include "AU/AudioUnitSDK/AUInputElement.cpp" | |||||
#include "AU/AudioUnitSDK/AUMIDIBase.cpp" | |||||
#include "AU/AudioUnitSDK/AUMIDIEffectBase.cpp" | |||||
#include "AU/AudioUnitSDK/AUOutputElement.cpp" | |||||
#include "AU/AudioUnitSDK/AUPlugInDispatch.cpp" | |||||
#include "AU/AudioUnitSDK/AUScopeElement.cpp" | |||||
#include "AU/AudioUnitSDK/ComponentBase.cpp" | |||||
#include "AU/AudioUnitSDK/MusicDeviceBase.cpp" | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUBase.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUBuffer.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUBufferAllocator.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUEffectBase.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUInputElement.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUMIDIBase.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUMIDIEffectBase.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUOutputElement.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUPlugInDispatch.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/AUScopeElement.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/ComponentBase.cpp> | |||||
#include <juce_audio_plugin_client/AU/AudioUnitSDK/MusicDeviceBase.cpp> | |||||
#undef verify | #undef verify | ||||
#undef verify_noerr | #undef verify_noerr | ||||
@@ -31,7 +31,152 @@ | |||||
#error To compile AudioUnitv3 and/or Standalone plug-ins, you need to add the juce_audio_utils and juce_audio_devices modules! | #error To compile AudioUnitv3 and/or Standalone plug-ins, you need to add the juce_audio_utils and juce_audio_devices modules! | ||||
#endif | #endif | ||||
#include "Standalone/juce_StandaloneFilterApp.cpp" | |||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#include <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h> | |||||
#include <juce_audio_plugin_client/detail/juce_WindowsHooks.h> | |||||
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h> | |||||
#include <juce_audio_devices/juce_audio_devices.h> | |||||
#include <juce_gui_extra/juce_gui_extra.h> | |||||
#include <juce_audio_utils/juce_audio_utils.h> | |||||
// You can set this flag in your build if you need to specify a different | |||||
// standalone JUCEApplication class for your app to use. If you don't | |||||
// set it then by default we'll just create a simple one as below. | |||||
#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | |||||
#include <juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h> | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
class StandaloneFilterApp : public JUCEApplication | |||||
{ | |||||
public: | |||||
StandaloneFilterApp() | |||||
{ | |||||
PropertiesFile::Options options; | |||||
options.applicationName = getApplicationName(); | |||||
options.filenameSuffix = ".settings"; | |||||
options.osxLibrarySubFolder = "Application Support"; | |||||
#if JUCE_LINUX || JUCE_BSD | |||||
options.folderName = "~/.config"; | |||||
#else | |||||
options.folderName = ""; | |||||
#endif | |||||
appProperties.setStorageParameters (options); | |||||
} | |||||
const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } | |||||
const String getApplicationVersion() override { return JucePlugin_VersionString; } | |||||
bool moreThanOneInstanceAllowed() override { return true; } | |||||
void anotherInstanceStarted (const String&) override {} | |||||
virtual StandaloneFilterWindow* createWindow() | |||||
{ | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; | |||||
#endif | |||||
return new StandaloneFilterWindow (getApplicationName(), | |||||
LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), | |||||
appProperties.getUserSettings(), | |||||
false, {}, nullptr | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
, juce::Array<StandalonePluginHolder::PluginInOuts> (channels, juce::numElementsInArray (channels)) | |||||
#else | |||||
, {} | |||||
#endif | |||||
#if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE | |||||
, false | |||||
#endif | |||||
); | |||||
} | |||||
//============================================================================== | |||||
void initialise (const String&) override | |||||
{ | |||||
mainWindow.reset (createWindow()); | |||||
#if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE | |||||
Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); | |||||
#endif | |||||
mainWindow->setVisible (true); | |||||
} | |||||
void shutdown() override | |||||
{ | |||||
mainWindow = nullptr; | |||||
appProperties.saveIfNeeded(); | |||||
} | |||||
//============================================================================== | |||||
void systemRequestedQuit() override | |||||
{ | |||||
if (mainWindow.get() != nullptr) | |||||
mainWindow->pluginHolder->savePluginState(); | |||||
if (ModalComponentManager::getInstance()->cancelAllModalComponents()) | |||||
{ | |||||
Timer::callAfterDelay (100, []() | |||||
{ | |||||
if (auto app = JUCEApplicationBase::getInstance()) | |||||
app->systemRequestedQuit(); | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
quit(); | |||||
} | |||||
} | |||||
protected: | |||||
ApplicationProperties appProperties; | |||||
std::unique_ptr<StandaloneFilterWindow> mainWindow; | |||||
}; | |||||
} // namespace juce | |||||
#if JucePlugin_Build_Standalone && JUCE_IOS | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") | |||||
using namespace juce; | |||||
bool JUCE_CALLTYPE juce_isInterAppAudioConnected() | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
return holder->isInterAppAudioConnected(); | |||||
return false; | |||||
} | |||||
void JUCE_CALLTYPE juce_switchToHostApplication() | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
holder->switchToHostApplication(); | |||||
} | |||||
Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) | |||||
{ | |||||
if (auto holder = StandalonePluginHolder::getInstance()) | |||||
return holder->getIAAHostIcon (size); | |||||
return Image(); | |||||
} | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif | |||||
#endif | |||||
#if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | #if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | ||||
extern juce::JUCEApplicationBase* juce_CreateApplication(); | extern juce::JUCEApplicationBase* juce_CreateApplication(); | ||||
@@ -23,4 +23,752 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include "Unity/juce_Unity_Wrapper.cpp" | |||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#if JucePlugin_Build_Unity | |||||
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h> | |||||
#include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | |||||
#if JUCE_WINDOWS | |||||
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h> | |||||
#endif | |||||
#include <juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h> | |||||
//============================================================================== | |||||
namespace juce | |||||
{ | |||||
typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); | |||||
extern createUnityPeerFunctionType juce_createUnityPeerFn; | |||||
//============================================================================== | |||||
class UnityPeer : public ComponentPeer, | |||||
public AsyncUpdater | |||||
{ | |||||
public: | |||||
UnityPeer (Component& ed) | |||||
: ComponentPeer (ed, 0), | |||||
mouseWatcher (*this) | |||||
{ | |||||
getEditor().setResizable (false, false); | |||||
} | |||||
//============================================================================== | |||||
Rectangle<int> getBounds() const override { return bounds; } | |||||
Point<float> localToGlobal (Point<float> relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } | |||||
Point<float> globalToLocal (Point<float> screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } | |||||
using ComponentPeer::localToGlobal; | |||||
using ComponentPeer::globalToLocal; | |||||
StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } | |||||
void setBounds (const Rectangle<int>& newBounds, bool) override | |||||
{ | |||||
bounds = newBounds; | |||||
mouseWatcher.setBoundsToWatch (bounds); | |||||
} | |||||
bool contains (Point<int> localPos, bool) const override | |||||
{ | |||||
if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) | |||||
&& isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) | |||||
return true; | |||||
return false; | |||||
} | |||||
void handleAsyncUpdate() override | |||||
{ | |||||
fillPixels(); | |||||
} | |||||
//============================================================================== | |||||
AudioProcessorEditor& getEditor() { return *dynamic_cast<AudioProcessorEditor*> (&getComponent()); } | |||||
void setPixelDataHandle (uint8* handle, int width, int height) | |||||
{ | |||||
pixelData = handle; | |||||
textureWidth = width; | |||||
textureHeight = height; | |||||
renderImage = Image (new UnityBitmapImage (pixelData, width, height)); | |||||
} | |||||
// N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. | |||||
// It works for our purposes here but a much more efficient way would be to use a GL texture. | |||||
void fillPixels() | |||||
{ | |||||
if (pixelData == nullptr) | |||||
return; | |||||
LowLevelGraphicsSoftwareRenderer renderer (renderImage); | |||||
renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); | |||||
handlePaint (renderer); | |||||
for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) | |||||
{ | |||||
auto r = pixelData[i + 2]; | |||||
auto g = pixelData[i + 1]; | |||||
auto b = pixelData[i + 0]; | |||||
pixelData[i + 0] = r; | |||||
pixelData[i + 1] = g; | |||||
pixelData[i + 2] = b; | |||||
} | |||||
} | |||||
void forwardMouseEvent (Point<float> position, ModifierKeys mods) | |||||
{ | |||||
ModifierKeys::currentModifiers = mods; | |||||
handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, | |||||
juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); | |||||
} | |||||
void forwardKeyPress (int code, String name, ModifierKeys mods) | |||||
{ | |||||
ModifierKeys::currentModifiers = mods; | |||||
handleKeyPress (getKeyPress (code, name)); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
struct UnityBitmapImage : public ImagePixelData | |||||
{ | |||||
UnityBitmapImage (uint8* data, int w, int h) | |||||
: ImagePixelData (Image::PixelFormat::ARGB, w, h), | |||||
imageData (data), | |||||
lineStride (width * pixelStride) | |||||
{ | |||||
} | |||||
std::unique_ptr<ImageType> createType() const override | |||||
{ | |||||
return std::make_unique<SoftwareImageType>(); | |||||
} | |||||
std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override | |||||
{ | |||||
return std::make_unique<LowLevelGraphicsSoftwareRenderer> (Image (this)); | |||||
} | |||||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override | |||||
{ | |||||
const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; | |||||
bitmap.data = imageData + offset; | |||||
bitmap.size = (size_t) (lineStride * height) - offset; | |||||
bitmap.pixelFormat = pixelFormat; | |||||
bitmap.lineStride = lineStride; | |||||
bitmap.pixelStride = pixelStride; | |||||
} | |||||
ImagePixelData::Ptr clone() override | |||||
{ | |||||
auto im = new UnityBitmapImage (imageData, width, height); | |||||
for (int i = 0; i < height; ++i) | |||||
memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); | |||||
return im; | |||||
} | |||||
uint8* imageData; | |||||
int pixelStride = 4, lineStride; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) | |||||
}; | |||||
//============================================================================== | |||||
struct MouseWatcher : public Timer | |||||
{ | |||||
MouseWatcher (ComponentPeer& o) : owner (o) {} | |||||
void timerCallback() override | |||||
{ | |||||
auto pos = Desktop::getMousePosition(); | |||||
if (boundsToWatch.contains (pos) && pos != lastMousePos) | |||||
{ | |||||
auto ms = Desktop::getInstance().getMainMouseSource(); | |||||
if (! ms.getCurrentModifiers().isLeftButtonDown()) | |||||
owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, | |||||
juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); | |||||
lastMousePos = pos; | |||||
} | |||||
} | |||||
void setBoundsToWatch (Rectangle<int> b) | |||||
{ | |||||
if (boundsToWatch != b) | |||||
boundsToWatch = b; | |||||
startTimer (250); | |||||
} | |||||
ComponentPeer& owner; | |||||
Rectangle<int> boundsToWatch; | |||||
Point<int> lastMousePos; | |||||
}; | |||||
//============================================================================== | |||||
KeyPress getKeyPress (int keyCode, String name) | |||||
{ | |||||
if (keyCode >= 32 && keyCode <= 64) | |||||
return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; | |||||
if (keyCode >= 91 && keyCode <= 122) | |||||
return { keyCode, ModifierKeys::currentModifiers, name[0] }; | |||||
if (keyCode >= 256 && keyCode <= 265) | |||||
return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; | |||||
if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; | |||||
if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; | |||||
return {}; | |||||
} | |||||
//============================================================================== | |||||
Rectangle<int> bounds; | |||||
MouseWatcher mouseWatcher; | |||||
uint8* pixelData = nullptr; | |||||
int textureWidth, textureHeight; | |||||
Image renderImage; | |||||
//============================================================================== | |||||
void setMinimised (bool) override {} | |||||
bool isMinimised() const override { return false; } | |||||
void setFullScreen (bool) override {} | |||||
bool isFullScreen() const override { return false; } | |||||
bool setAlwaysOnTop (bool) override { return false; } | |||||
void toFront (bool) override {} | |||||
void toBehind (ComponentPeer*) override {} | |||||
bool isFocused() const override { return true; } | |||||
void grabFocus() override {} | |||||
void* getNativeHandle() const override { return nullptr; } | |||||
OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } | |||||
BorderSize<int> getFrameSize() const override { return {}; } | |||||
void setVisible (bool) override {} | |||||
void setTitle (const String&) override {} | |||||
void setIcon (const Image&) override {} | |||||
void textInputRequired (Point<int>, TextInputTarget&) override {} | |||||
void setAlpha (float) override {} | |||||
void performAnyPendingRepaintsNow() override {} | |||||
void repaint (const Rectangle<int>&) override {} | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) | |||||
}; | |||||
static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } | |||||
//============================================================================== | |||||
class AudioProcessorUnityWrapper | |||||
{ | |||||
public: | |||||
AudioProcessorUnityWrapper (bool isTemporary) | |||||
{ | |||||
pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); | |||||
if (! isTemporary && pluginInstance->hasEditor()) | |||||
{ | |||||
pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); | |||||
pluginInstanceEditor->setVisible (true); | |||||
detail::PluginUtilities::addToDesktop (*pluginInstanceEditor, nullptr); | |||||
} | |||||
juceParameters.update (*pluginInstance, false); | |||||
} | |||||
~AudioProcessorUnityWrapper() | |||||
{ | |||||
if (pluginInstanceEditor != nullptr) | |||||
{ | |||||
pluginInstanceEditor->removeFromDesktop(); | |||||
PopupMenu::dismissAllActiveMenus(); | |||||
pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); | |||||
pluginInstanceEditor = nullptr; | |||||
} | |||||
} | |||||
void create (UnityAudioEffectState* state) | |||||
{ | |||||
// only supported in Unity plugin API > 1.0 | |||||
if (state->structSize >= sizeof (UnityAudioEffectState)) | |||||
samplesPerBlock = static_cast<int> (state->dspBufferSize); | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||||
const int numConfigs = sizeof (configs) / sizeof (short[2]); | |||||
jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); | |||||
pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); | |||||
#else | |||||
pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); | |||||
#endif | |||||
pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); | |||||
scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); | |||||
} | |||||
void release() | |||||
{ | |||||
pluginInstance->releaseResources(); | |||||
} | |||||
void reset() | |||||
{ | |||||
pluginInstance->reset(); | |||||
} | |||||
void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) | |||||
{ | |||||
// If the plugin has a bypass parameter, set it to the current bypass state | |||||
if (auto* param = pluginInstance->getBypassParameter()) | |||||
if (isBypassed != (param->getValue() >= 0.5f)) | |||||
param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); | |||||
for (int pos = 0; pos < bufferSize;) | |||||
{ | |||||
auto max = jmin (bufferSize - pos, samplesPerBlock); | |||||
processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); | |||||
pos += max; | |||||
} | |||||
} | |||||
void declareParameters (UnityAudioEffectDefinition& definition) | |||||
{ | |||||
static std::unique_ptr<UnityAudioParameterDefinition> parametersPtr; | |||||
static int numParams = 0; | |||||
if (parametersPtr == nullptr) | |||||
{ | |||||
numParams = (int) juceParameters.size(); | |||||
parametersPtr.reset (static_cast<UnityAudioParameterDefinition*> (std::calloc (static_cast<size_t> (numParams), | |||||
sizeof (UnityAudioParameterDefinition)))); | |||||
parameterDescriptions.clear(); | |||||
for (int i = 0; i < numParams; ++i) | |||||
{ | |||||
auto* parameter = juceParameters.getParamForIndex (i); | |||||
auto& paramDef = parametersPtr.get()[i]; | |||||
const auto nameLength = (size_t) numElementsInArray (paramDef.name); | |||||
const auto unitLength = (size_t) numElementsInArray (paramDef.unit); | |||||
parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); | |||||
if (parameter->getLabel().isNotEmpty()) | |||||
parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); | |||||
parameterDescriptions.add (parameter->getName (15)); | |||||
paramDef.description = parameterDescriptions[i].toRawUTF8(); | |||||
paramDef.defaultVal = parameter->getDefaultValue(); | |||||
paramDef.min = 0.0f; | |||||
paramDef.max = 1.0f; | |||||
paramDef.displayScale = 1.0f; | |||||
paramDef.displayExponent = 1.0f; | |||||
} | |||||
} | |||||
definition.numParameters = static_cast<uint32> (numParams); | |||||
definition.parameterDefintions = parametersPtr.get(); | |||||
} | |||||
void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } | |||||
float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } | |||||
String getParameterString (int index) const noexcept | |||||
{ | |||||
auto* param = juceParameters.getParamForIndex (index); | |||||
return param->getText (param->getValue(), 16); | |||||
} | |||||
int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } | |||||
int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } | |||||
bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } | |||||
UnityPeer& getEditorPeer() const | |||||
{ | |||||
auto* peer = dynamic_cast<UnityPeer*> (pluginInstanceEditor->getPeer()); | |||||
jassert (peer != nullptr); | |||||
return *peer; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) | |||||
{ | |||||
int ch; | |||||
for (ch = 0; ch < numInChannels; ++ch) | |||||
{ | |||||
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>; | |||||
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const>; | |||||
DstSampleType dstData (scratchBuffer.getWritePointer (ch)); | |||||
SrcSampleType srcData (inBuffer + ch, numInChannels); | |||||
dstData.convertSamples (srcData, bufferSize); | |||||
} | |||||
for (; ch < numOutChannels; ++ch) | |||||
scratchBuffer.clear (ch, 0, bufferSize); | |||||
{ | |||||
const ScopedLock sl (pluginInstance->getCallbackLock()); | |||||
if (pluginInstance->isSuspended()) | |||||
{ | |||||
scratchBuffer.clear(); | |||||
} | |||||
else | |||||
{ | |||||
MidiBuffer mb; | |||||
if (isBypassed && pluginInstance->getBypassParameter() == nullptr) | |||||
pluginInstance->processBlockBypassed (scratchBuffer, mb); | |||||
else | |||||
pluginInstance->processBlock (scratchBuffer, mb); | |||||
} | |||||
} | |||||
for (ch = 0; ch < numOutChannels; ++ch) | |||||
{ | |||||
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst>; | |||||
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>; | |||||
DstSampleType dstData (outBuffer + ch, numOutChannels); | |||||
SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); | |||||
dstData.convertSamples (srcData, bufferSize); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
std::unique_ptr<AudioProcessor> pluginInstance; | |||||
std::unique_ptr<AudioProcessorEditor> pluginInstanceEditor; | |||||
int samplesPerBlock = 1024; | |||||
StringArray parameterDescriptions; | |||||
AudioBuffer<float> scratchBuffer; | |||||
LegacyAudioParametersWrapper juceParameters; | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) | |||||
}; | |||||
//============================================================================== | |||||
static HashMap<int, AudioProcessorUnityWrapper*>& getWrapperMap() | |||||
{ | |||||
static HashMap<int, AudioProcessorUnityWrapper*> wrapperMap; | |||||
return wrapperMap; | |||||
} | |||||
static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) | |||||
{ | |||||
getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); | |||||
} | |||||
static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) | |||||
{ | |||||
getWrapperMap().removeValue (wrapperToRemove); | |||||
} | |||||
//============================================================================== | |||||
static UnityAudioEffectDefinition getEffectDefinition() | |||||
{ | |||||
const auto wrapper = std::make_unique<AudioProcessorUnityWrapper> (true); | |||||
const String originalName { JucePlugin_Name }; | |||||
const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; | |||||
UnityAudioEffectDefinition result{}; | |||||
name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); | |||||
result.structSize = sizeof (UnityAudioEffectDefinition); | |||||
result.parameterStructSize = sizeof (UnityAudioParameterDefinition); | |||||
result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; | |||||
result.pluginVersion = JucePlugin_VersionCode; | |||||
// effects must set this to 0, generators > 0 | |||||
result.channels = (wrapper->getNumInputChannels() != 0 ? 0 | |||||
: static_cast<uint32> (wrapper->getNumOutputChannels())); | |||||
wrapper->declareParameters (result); | |||||
result.create = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = new AudioProcessorUnityWrapper (false); | |||||
pluginInstance->create (state); | |||||
state->effectData = pluginInstance; | |||||
onWrapperCreation (pluginInstance); | |||||
return 0; | |||||
}; | |||||
result.release = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->release(); | |||||
onWrapperDeletion (pluginInstance); | |||||
delete pluginInstance; | |||||
if (getWrapperMap().size() == 0) | |||||
shutdownJuce_GUI(); | |||||
return 0; | |||||
}; | |||||
result.reset = [] (UnityAudioEffectState* state) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->reset(); | |||||
return 0; | |||||
}; | |||||
result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) | |||||
{ | |||||
ignoreUnused (state, pos); | |||||
return 0; | |||||
}; | |||||
result.process = [] (UnityAudioEffectState* state, | |||||
float* inBuffer, | |||||
float* outBuffer, | |||||
unsigned int bufferSize, | |||||
int numInChannels, | |||||
int numOutChannels) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
if (pluginInstance != nullptr) | |||||
{ | |||||
auto isPlaying = ((state->flags & stateIsPlaying) != 0); | |||||
auto isMuted = ((state->flags & stateIsMuted) != 0); | |||||
auto isPaused = ((state->flags & stateIsPaused) != 0); | |||||
const auto bypassed = ! isPlaying || (isMuted || isPaused); | |||||
pluginInstance->process (inBuffer, outBuffer, static_cast<int> (bufferSize), numInChannels, numOutChannels, bypassed); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::clear (outBuffer, static_cast<int> (bufferSize) * numOutChannels); | |||||
} | |||||
return 0; | |||||
}; | |||||
result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
pluginInstance->setParameter (index, value); | |||||
return 0; | |||||
}; | |||||
result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
*value = pluginInstance->getParameter (index); | |||||
pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); | |||||
return 0; | |||||
}; | |||||
result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) | |||||
{ | |||||
ignoreUnused (numSamples); | |||||
const StringRef kindStr { kind }; | |||||
if (kindStr == StringRef ("Editor")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; | |||||
} | |||||
else if (kindStr == StringRef ("ID")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
for (HashMap<int, AudioProcessorUnityWrapper*>::Iterator i (getWrapperMap()); i.next();) | |||||
{ | |||||
if (i.getValue() == pluginInstance) | |||||
{ | |||||
buffer[0] = (float) i.getKey(); | |||||
break; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
else if (kindStr == StringRef ("Size")) | |||||
{ | |||||
auto* pluginInstance = state->getEffectData<AudioProcessorUnityWrapper>(); | |||||
auto& editor = pluginInstance->getEditorPeer().getEditor(); | |||||
buffer[0] = (float) editor.getBounds().getWidth(); | |||||
buffer[1] = (float) editor.getBounds().getHeight(); | |||||
buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); | |||||
buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); | |||||
buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); | |||||
buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); | |||||
} | |||||
return 0; | |||||
}; | |||||
return result; | |||||
} | |||||
} // namespace juce | |||||
// From reading the example code, it seems that the triple indirection indicates | |||||
// an out-value of an array of pointers. That is, after calling this function, definitionsPtr | |||||
// should point to a pre-existing/static array of pointer-to-effect-definition. | |||||
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) | |||||
{ | |||||
if (juce::getWrapperMap().size() == 0) | |||||
juce::initialiseJuce_GUI(); | |||||
static std::once_flag flag; | |||||
std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); | |||||
static auto definition = juce::getEffectDefinition(); | |||||
static UnityAudioEffectDefinition* definitions[] { &definition }; | |||||
*definitionsPtr = definitions; | |||||
return 1; | |||||
} | |||||
//============================================================================== | |||||
static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) | |||||
{ | |||||
int flags = 0; | |||||
if (mouseDown) | |||||
{ | |||||
if (mouseButton == 0) | |||||
flags |= juce::ModifierKeys::leftButtonModifier; | |||||
else if (mouseButton == 1) | |||||
flags |= juce::ModifierKeys::rightButtonModifier; | |||||
else if (mouseButton == 2) | |||||
flags |= juce::ModifierKeys::middleButtonModifier; | |||||
} | |||||
if (mods == 0) | |||||
return flags; | |||||
if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; | |||||
if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; | |||||
if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; | |||||
if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; | |||||
return { flags }; | |||||
} | |||||
//============================================================================== | |||||
static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) | |||||
{ | |||||
auto* wrapper = juce::getWrapperMap()[id]; | |||||
jassert (wrapper != nullptr); | |||||
return wrapper; | |||||
} | |||||
//============================================================================== | |||||
static void UNITY_INTERFACE_API onRenderEvent (int id) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); | |||||
} | |||||
UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() | |||||
{ | |||||
return onRenderEvent; | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast<juce::uint8*> (data), w, h); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); | |||||
} | |||||
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) | |||||
{ | |||||
getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_WINDOWS | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") | |||||
extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) | |||||
{ | |||||
if (reason == DLL_PROCESS_ATTACH) | |||||
juce::Process::setCurrentModuleInstanceHandle (instance); | |||||
return true; | |||||
} | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif | |||||
#endif |
@@ -23,4 +23,4 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include "detail/juce_PluginUtilities.cpp" | |||||
#include "juce_audio_plugin_client_VST2.cpp" |
@@ -23,6 +23,4 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#if JucePlugin_Build_VST || JucePlugin_Build_VST3 | |||||
#include "detail/juce_VSTWindowUtilities.mm" | |||||
#endif | |||||
#include "juce_audio_plugin_client_VST3.cpp" |
@@ -27,7 +27,7 @@ extern HWND juce_messageWindowHandle; | |||||
namespace detail | namespace detail | ||||
{ | { | ||||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity | |||||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client | |||||
bool isRunningInUnity(); | bool isRunningInUnity(); | ||||
#else | #else | ||||
constexpr bool isRunningInUnity() { return false; } | constexpr bool isRunningInUnity() { return false; } | ||||
@@ -50,7 +50,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type") | |||||
#endif | #endif | ||||
void juce_repeatLastProcessPriority(); | void juce_repeatLastProcessPriority(); | ||||
bool juce_isRunningInWine(); | |||||
using CheckEventBlockedByModalComps = bool (*) (const MSG&); | using CheckEventBlockedByModalComps = bool (*) (const MSG&); | ||||
extern CheckEventBlockedByModalComps isEventBlockedByModalComps; | extern CheckEventBlockedByModalComps isEventBlockedByModalComps; | ||||