diff --git a/extras/Projucer/Builds/LinuxMakefile/Makefile b/extras/Projucer/Builds/LinuxMakefile/Makefile index 18739e54cf..0da71e4666 100644 --- a/extras/Projucer/Builds/LinuxMakefile/Makefile +++ b/extras/Projucer/Builds/LinuxMakefile/Makefile @@ -36,7 +36,7 @@ ifeq ($(CONFIG),Debug) endif JUCE_CPPFLAGS := $(DEPFLAGS) -DLINUX=1 -DDEBUG=1 -D_DEBUG=1 -DJUCER_LINUX_MAKE_6D53C8B4=1 -DJUCE_APP_VERSION=5.3.2 -DJUCE_APP_VERSION_HEX=0x50302 $(shell pkg-config --cflags freetype2 libcurl x11 xext xinerama webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS) - JUCE_CPPFLAGS_APP := -DJucePlugin_Build_VST=0 -DJucePlugin_Build_VST3=0 -DJucePlugin_Build_AU=0 -DJucePlugin_Build_AUv3=0 -DJucePlugin_Build_RTAS=0 -DJucePlugin_Build_AAX=0 -DJucePlugin_Build_Standalone=0 + JUCE_CPPFLAGS_APP := -DJucePlugin_Build_VST=0 -DJucePlugin_Build_VST3=0 -DJucePlugin_Build_AU=0 -DJucePlugin_Build_AUv3=0 -DJucePlugin_Build_RTAS=0 -DJucePlugin_Build_AAX=0 -DJucePlugin_Build_Standalone=0 -DJucePlugin_Build_Unity=0 JUCE_TARGET_APP := Projucer JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 $(CFLAGS) @@ -57,7 +57,7 @@ ifeq ($(CONFIG),Release) endif JUCE_CPPFLAGS := $(DEPFLAGS) -DLINUX=1 -DNDEBUG=1 -DJUCER_LINUX_MAKE_6D53C8B4=1 -DJUCE_APP_VERSION=5.3.2 -DJUCE_APP_VERSION_HEX=0x50302 $(shell pkg-config --cflags freetype2 libcurl x11 xext xinerama webkit2gtk-4.0 gtk+-x11-3.0) -pthread -I../../JuceLibraryCode -I../../../../modules $(CPPFLAGS) - JUCE_CPPFLAGS_APP := -DJucePlugin_Build_VST=0 -DJucePlugin_Build_VST3=0 -DJucePlugin_Build_AU=0 -DJucePlugin_Build_AUv3=0 -DJucePlugin_Build_RTAS=0 -DJucePlugin_Build_AAX=0 -DJucePlugin_Build_Standalone=0 + JUCE_CPPFLAGS_APP := -DJucePlugin_Build_VST=0 -DJucePlugin_Build_VST3=0 -DJucePlugin_Build_AU=0 -DJucePlugin_Build_AUv3=0 -DJucePlugin_Build_RTAS=0 -DJucePlugin_Build_AAX=0 -DJucePlugin_Build_Standalone=0 -DJucePlugin_Build_Unity=0 JUCE_TARGET_APP := Projucer JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -O3 $(CFLAGS) diff --git a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj index 27a8ec6277..bc90023c6c 100644 --- a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj +++ b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj @@ -233,6 +233,7 @@ 72ED72174F9DBD0ABD8AFCED = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_PaintElementImage.cpp"; path = "../../Source/ComponentEditor/PaintElements/jucer_PaintElementImage.cpp"; sourceTree = "SOURCE_ROOT"; }; 73DE14CEAD25D3445457013E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_SliderHandler.h"; path = "../../Source/ComponentEditor/Components/jucer_SliderHandler.h"; sourceTree = "SOURCE_ROOT"; }; 75BE2887C6F324B818D80A21 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_SnapGridPainter.h"; path = "../../Source/ComponentEditor/UI/jucer_SnapGridPainter.h"; sourceTree = "SOURCE_ROOT"; }; + 763A63E75AC802F17D11FE8B = {isa = PBXFileReference; lastKnownFileType = file.cs; name = "jucer_UnityPluginGUIScript.cs"; path = "../../Source/BinaryData/Templates/jucer_UnityPluginGUIScript.cs"; sourceTree = "SOURCE_ROOT"; }; 7687A1374C60A025BDBE98DE = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_PointComponent.h"; path = "../../Source/ComponentEditor/PaintElements/jucer_PointComponent.h"; sourceTree = "SOURCE_ROOT"; }; 77EA01E7D04BF889930BFF54 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "jucer_PaintElementRoundedRectangle.h"; path = "../../Source/ComponentEditor/PaintElements/jucer_PaintElementRoundedRectangle.h"; sourceTree = "SOURCE_ROOT"; }; 78D0DBC4798FF040FDB90F6D = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "jucer_GeneratedCode.cpp"; path = "../../Source/ComponentEditor/jucer_GeneratedCode.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -493,7 +494,8 @@ 3F7C5B53347A487C7FBD2223, 4ECF029E3A69BF42FED1503D, 3DC2ED15A9DFAAEF3D2ACDDF, - E67999BF57B139E00207A374, ); name = Templates; sourceTree = ""; }; + E67999BF57B139E00207A374, + 763A63E75AC802F17D11FE8B, ); name = Templates; sourceTree = ""; }; A9399733CAA07BDAB958242C = {isa = PBXGroup; children = ( 8CF70DA9AB4725126B9F55BE, F0F189518721D46C0F94FD56, @@ -822,7 +824,8 @@ "JucePlugin_Build_AUv3=0", "JucePlugin_Build_RTAS=0", "JucePlugin_Build_AAX=0", - "JucePlugin_Build_Standalone=0", ); + "JucePlugin_Build_Standalone=0", + "JucePlugin_Build_Unity=0", ); GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ("../../JuceLibraryCode", "../../../../modules", "$(inherited)"); INFOPLIST_FILE = Info-App.plist; @@ -832,6 +835,7 @@ MACOSX_DEPLOYMENT_TARGET_ppc = 10.4; OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code"; PRODUCT_BUNDLE_IDENTIFIER = com.juce.theprojucer; + PRODUCT_NAME = "Projucer"; SDKROOT_ppc = macosx10.5; USE_HEADERMAP = NO; }; name = Debug; }; 0BC15DC2E5FE5ECFFB398D49 = {isa = XCBuildConfiguration; buildSettings = { @@ -855,7 +859,8 @@ "JucePlugin_Build_AUv3=0", "JucePlugin_Build_RTAS=0", "JucePlugin_Build_AAX=0", - "JucePlugin_Build_Standalone=0", ); + "JucePlugin_Build_Standalone=0", + "JucePlugin_Build_Unity=0", ); GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ("../../JuceLibraryCode", "../../../../modules", "$(inherited)"); @@ -867,6 +872,7 @@ MACOSX_DEPLOYMENT_TARGET_ppc = 10.4; OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code"; PRODUCT_BUNDLE_IDENTIFIER = com.juce.theprojucer; + PRODUCT_NAME = "Projucer"; SDKROOT_ppc = macosx10.5; USE_HEADERMAP = NO; }; name = Release; }; C42924A24AB55E6A940423EA = {isa = XCBuildConfiguration; buildSettings = { diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj index 2f2992ca0a..4893047fd1 100644 --- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj @@ -74,7 +74,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreadedDebugDLL true @@ -115,7 +115,7 @@ Full ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreaded true @@ -2065,6 +2065,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters index 4163efab29..4cd4ac1f15 100644 --- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters @@ -3662,6 +3662,9 @@ Projucer\BinaryData\Icons + + Projucer\BinaryData\Templates + Projucer\BinaryData diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj index a7039ba26a..be53eedaee 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj @@ -74,7 +74,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreadedDebugDLL true @@ -115,7 +115,7 @@ Full ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreaded true @@ -2065,6 +2065,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters index 8d2da9e4f2..bb6181ed47 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters @@ -3662,6 +3662,9 @@ Projucer\BinaryData\Icons + + Projucer\BinaryData\Templates + Projucer\BinaryData diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index cd8b1d4a7d..1146d3fc77 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -74,7 +74,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreadedDebug true @@ -115,7 +115,7 @@ Full ..\..\JuceLibraryCode;..\..\..\..\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCER_VS2017_78A5024=1;JUCE_APP_VERSION=5.3.2;JUCE_APP_VERSION_HEX=0x50302;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreaded true @@ -2065,6 +2065,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 42313cdb1e..8c37fa827a 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -3662,6 +3662,9 @@ Projucer\BinaryData\Icons + + Projucer\BinaryData\Templates + Projucer\BinaryData diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.cpp b/extras/Projucer/JuceLibraryCode/BinaryData.cpp index ff35bfd947..2d3d297e11 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.cpp +++ b/extras/Projucer/JuceLibraryCode/BinaryData.cpp @@ -7267,8 +7267,194 @@ static const unsigned char temp_binary_data_54[] = const char* jucer_PIPTemplate_h = (const char*) temp_binary_data_54; -//================== colourscheme_dark.xml ================== +//================== jucer_UnityPluginGUIScript.cs ================== static const unsigned char temp_binary_data_55[] = +"#if UNITY_EDITOR\n" +"\n" +"using UnityEditor;\n" +"using UnityEngine;\n" +"\n" +"using System.Collections.Generic;\n" +"using System.Runtime.InteropServices;\n" +"\n" +"public class %%plugin_name%%GUI : IAudioEffectPluginGUI\n" +"{\n" +" public override string Name { get { return \"%%plugin_name%%\"; } }\n" +" public override string Description { get { return \"%%plugin_description%%\"; } }\n" +" public override string Vendor { get { return \"%%plugin_vendor%%\"; } }\n" +"\n" +" //==============================================================================\n" +"\t[DllImport(\"%%plugin_name%%\")] static extern System.IntPtr getRenderCallback();\n" +"\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unityInitialiseTexture (int id, System.IntPtr texture, int width, int height);\n" +"\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unityMouseDown (int id, float x, float y, EventModifiers mods, int button);\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unityMouseDrag (int id, float x, float y, EventModifiers mods, int button);\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unityMouseUp (int id, float x, float y, EventModifiers mods);\n" +"\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unityKeyEvent (int id, KeyCode code, EventModifiers mods, string name);\n" +"\n" +" [DllImport(\"%%plugin_name%%\")] static extern void unitySetScreenBounds (int id, float x, float y, float w, float h);\n" +"\n" +" //==============================================================================\n" +" private class PluginGUIInstance\n" +" {\n" +" public PluginGUIInstance (ref IAudioEffectPlugin plugin, int id)\n" +" {\n" +" instanceID = id;\n" +"\n" +" float[] arr;\n" +" plugin.GetFloatBuffer (\"Editor\", out arr, 1);\n" +" hasEditor = (arr[0] > 0.0f);\n" +" }\n" +"\n" +" public void repaint (Rect r)\n" +" { \n" +" Vector2 newScreenPosition = GUIUtility.GUIToScreenPoint (r.position);\n" +"\n" +" if (bounds != r \n" +" || screenPosition != newScreenPosition)\n" +" {\n" +" screenPosition = newScreenPosition;\n" +" bounds = r;\n" +"\n" +" unitySetScreenBounds (instanceID, screenPosition.x, screenPosition.y, bounds.width, bounds.height);\n" +" setupTexture();\n" +" }\n" +"\n" +"\t\t\tGL.IssuePluginEvent (getRenderCallback(), instanceID);\n" +"\n" +" texture.SetPixels32 (pixels);\n" +" texture.Apply();\n" +"\n" +" EditorGUI.DrawPreviewTexture (bounds, texture);\n" +" }\n" +"\n" +" public bool handleMouseEvent (EventType eventType)\n" +" {\n" +" Vector2 mousePos = Event.current.mousePosition;\n" +" EventModifiers mods = Event.current.modifiers;\n" +"\n" +" if (! bounds.Contains (mousePos))\n" +" return false;\n" +"\n" +" Vector2 relativePos = new Vector2 (mousePos.x - bounds.x, mousePos.y - bounds.y);\n" +"\n" +" if (eventType == EventType.MouseDown) \n" +" {\n" +" unityMouseDown (instanceID, relativePos.x, relativePos.y, mods, Event.current.button);\n" +" GUIUtility.hotControl = GUIUtility.GetControlID (FocusType.Passive);\n" +" }\n" +" else if (eventType == EventType.MouseUp)\n" +" {\n" +" unityMouseUp (instanceID, relativePos.x, relativePos.y, mods);\n" +" GUIUtility.hotControl = 0;\n" +" }\n" +" else if (eventType == EventType.MouseDrag) \n" +" {\n" +" unityMouseDrag (instanceID, relativePos.x, relativePos.y, mods, Event.current.button);\n" +" }\n" +"\n" +" Event.current.Use();\n" +"\n" +" return true;\n" +" }\n" +"\n" +" public void handleKeyEvent (EventType eventType)\n" +" {\n" +" if (eventType == EventType.KeyDown)\n" +" {\n" +" KeyCode code = Event.current.keyCode;\n" +"\n" +" if (code == KeyCode.None)\n" +" return;\n" +"\n" +" EventModifiers mods = Event.current.modifiers;\n" +"\n" +" unityKeyEvent (instanceID, code, mods, code.ToString());\n" +" }\n" +" }\n" +"\n" +" private void setupTexture()\n" +" {\n" +" if (pixelHandle.IsAllocated)\n" +" pixelHandle.Free();\n" +"\n" +" texture = new Texture2D ((int) bounds.width, (int) bounds.height, TextureFormat.ARGB32, false);\n" +"\n" +" pixels = texture.GetPixels32();\n" +" pixelHandle = GCHandle.Alloc (pixels, GCHandleType.Pinned);\n" +"\n" +" unityInitialiseTexture (instanceID, pixelHandle.AddrOfPinnedObject(), texture.width, texture.height);\n" +" }\n" +"\n" +" public int instanceID = -1;\n" +" public bool hasEditor;\n" +"\n" +" private Vector2 screenPosition;\n" +" private Rect bounds;\n" +"\n" +" private Texture2D texture;\n" +" private Color32[] pixels;\n" +" private GCHandle pixelHandle;\n" +" }\n" +" List guis = new List();\n" +"\n" +" private PluginGUIInstance getGUIInstanceForPlugin (ref IAudioEffectPlugin plugin)\n" +" {\n" +" float[] idArray;\n" +" plugin.GetFloatBuffer (\"ID\", out idArray, 1);\n" +"\n" +" int id = (int) idArray[0];\n" +"\n" +" for (int i = 0; i < guis.Count; ++i)\n" +" {\n" +" if (guis[i].instanceID == id)\n" +" return guis[i];\n" +" }\n" +"\n" +" PluginGUIInstance newInstance = new PluginGUIInstance (ref plugin, id);\n" +" guis.Add (newInstance);\n" +"\n" +" return guis[guis.Count - 1];\n" +" }\n" +"\n" +" //==============================================================================\n" +" public override bool OnGUI (IAudioEffectPlugin plugin)\n" +" {\n" +" PluginGUIInstance guiInstance = getGUIInstanceForPlugin (ref plugin);\n" +"\n" +" if (! guiInstance.hasEditor)\n" +" return true;\n" +"\n" +" float[] arr;\n" +" plugin.GetFloatBuffer (\"Size\", out arr, 6);\n" +"\n" +" Rect r = GUILayoutUtility.GetRect (arr[0], arr[1],\n" +" new GUILayoutOption[] { GUILayout.MinWidth (arr[2]), GUILayout.MinHeight (arr[3]),\n" +" GUILayout.MaxWidth (arr[4]), GUILayout.MaxHeight (arr[5]) });\n" +"\n" +" int controlID = GUIUtility.GetControlID (FocusType.Passive);\n" +" Event currentEvent = Event.current;\n" +" EventType currentEventType = currentEvent.GetTypeForControl (controlID);\n" +"\n" +" if (currentEventType == EventType.Repaint)\n" +" guiInstance.repaint (r);\n" +" else if (currentEvent.isMouse)\n" +" guiInstance.handleMouseEvent (currentEventType);\n" +" else if (currentEvent.isKey)\n" +" guiInstance.handleKeyEvent (currentEventType);\n" +"\n" +" return false;\n" +" }\n" +"}\n" +"\n" +"#endif"; + +const char* jucer_UnityPluginGUIScript_cs = (const char*) temp_binary_data_55; + +//================== colourscheme_dark.xml ================== +static const unsigned char temp_binary_data_56[] = "\r\n" "\r\n" "\r\n" @@ -7293,10 +7479,10 @@ static const unsigned char temp_binary_data_55[] = " \r\n" "\r\n"; -const char* colourscheme_dark_xml = (const char*) temp_binary_data_55; +const char* colourscheme_dark_xml = (const char*) temp_binary_data_56; //================== colourscheme_light.xml ================== -static const unsigned char temp_binary_data_56[] = +static const unsigned char temp_binary_data_57[] = "\r\n" "\r\n" "\r\n" @@ -7321,16 +7507,16 @@ static const unsigned char temp_binary_data_56[] = " \r\n" "\r\n"; -const char* colourscheme_light_xml = (const char*) temp_binary_data_56; +const char* colourscheme_light_xml = (const char*) temp_binary_data_57; //================== nothingtoseehere.txt ================== -static const unsigned char temp_binary_data_57[] = +static const unsigned char temp_binary_data_58[] = "VUEtMTk3NTkzMTgtNA=="; -const char* nothingtoseehere_txt = (const char*) temp_binary_data_57; +const char* nothingtoseehere_txt = (const char*) temp_binary_data_58; //================== offlinepage.html ================== -static const unsigned char temp_binary_data_58[] = +static const unsigned char temp_binary_data_59[] = "\n" " \n" " \n" @@ -7374,10 +7560,10 @@ static const unsigned char temp_binary_data_58[] = " \n" ""; -const char* offlinepage_html = (const char*) temp_binary_data_58; +const char* offlinepage_html = (const char*) temp_binary_data_59; //================== projucer_EULA.txt ================== -static const unsigned char temp_binary_data_59[] = +static const unsigned char temp_binary_data_60[] = "\r\n" "IMPORTANT NOTICE: PLEASE READ CAREFULLY BEFORE INSTALLING THE SOFTWARE:\r\n" "\r\n" @@ -7541,10 +7727,10 @@ static const unsigned char temp_binary_data_59[] = "\r\n" "10.6. Please note that this License, its subject matter and its formation, are governed by English law. You and we both agree to that the courts of England and Wales will have exclusive jurisdiction.\r\n"; -const char* projucer_EULA_txt = (const char*) temp_binary_data_59; +const char* projucer_EULA_txt = (const char*) temp_binary_data_60; //================== RecentFilesMenuTemplate.nib ================== -static const unsigned char temp_binary_data_60[] = +static const unsigned char temp_binary_data_61[] = { 98,112,108,105,115,116,48,48,212,0,1,0,2,0,3,0,4,0,5,0,6,1,53,1,54,88,36,118,101,114,115,105,111,110,88,36,111,98,106,101,99,116,115,89,36,97,114,99,104,105,118,101,114,84,36,116,111,112,18,0,1,134,160,175,16,74,0,7,0,8,0,31,0,35,0,36,0,42,0,46,0,50, 0,53,0,57,0,74,0,77,0,78,0,86,0,87,0,97,0,112,0,113,0,114,0,119,0,120,0,121,0,124,0,128,0,129,0,132,0,143,0,144,0,145,0,149,0,153,0,162,0,163,0,164,0,169,0,173,0,180,0,181,0,182,0,185,0,192,0,193,0,200,0,201,0,208,0,209,0,216,0,217,0,224,0,225,0,226, 0,229,0,230,0,232,0,249,1,11,1,29,1,30,1,31,1,32,1,33,1,34,1,35,1,36,1,37,1,38,1,39,1,40,1,41,1,42,1,43,1,44,1,47,1,50,85,36,110,117,108,108,219,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0,22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0, @@ -7581,7 +7767,7 @@ static const unsigned char temp_binary_data_60[] = 7,157,7,159,7,161,7,163,7,165,7,167,7,169,7,171,7,173,7,175,7,177,7,179,7,181,7,190,7,192,7,225,7,227,7,229,7,231,7,233,7,235,7,237,7,239,7,241,7,243,7,245,7,247,7,249,7,251,7,253,7,255,8,2,8,5,8,8,8,11,8,14,8,17,8,20,8,23,8,26,8,29,8,32,8,35,8,38,8, 41,8,44,8,53,8,55,8,56,8,65,8,67,8,68,8,77,8,92,8,97,8,115,8,120,8,134,0,0,0,0,0,0,2,2,0,0,0,0,0,0,1,57,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,136,0,0 }; -const char* RecentFilesMenuTemplate_nib = (const char*) temp_binary_data_60; +const char* RecentFilesMenuTemplate_nib = (const char*) temp_binary_data_61; const char* getNamedResource (const char* resourceNameUTF8, int& numBytes) @@ -7648,6 +7834,7 @@ const char* getNamedResource (const char* resourceNameUTF8, int& numBytes) case 0xbc050edc: numBytes = 4926; return jucer_PIPAudioProcessorTemplate_h; case 0xf4ca9e9a: numBytes = 2447; return jucer_PIPMain_cpp; case 0x0b16e320: numBytes = 517; return jucer_PIPTemplate_h; + case 0xcd472557: numBytes = 6426; return jucer_UnityPluginGUIScript_cs; case 0x763d39dc: numBytes = 1050; return colourscheme_dark_xml; case 0xe8b08520: numBytes = 1050; return colourscheme_light_xml; case 0x938e96ec: numBytes = 20; return nothingtoseehere_txt; @@ -7718,6 +7905,7 @@ const char* namedResourceList[] = "jucer_PIPAudioProcessorTemplate_h", "jucer_PIPMain_cpp", "jucer_PIPTemplate_h", + "jucer_UnityPluginGUIScript_cs", "colourscheme_dark_xml", "colourscheme_light_xml", "nothingtoseehere_txt", @@ -7783,6 +7971,7 @@ const char* originalFilenames[] = "jucer_PIPAudioProcessorTemplate.h", "jucer_PIPMain.cpp", "jucer_PIPTemplate.h", + "jucer_UnityPluginGUIScript.cs", "colourscheme_dark.xml", "colourscheme_light.xml", "nothingtoseehere.txt", diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.h b/extras/Projucer/JuceLibraryCode/BinaryData.h index 79fa6ece5e..1bd2997aec 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.h +++ b/extras/Projucer/JuceLibraryCode/BinaryData.h @@ -173,6 +173,9 @@ namespace BinaryData extern const char* jucer_PIPTemplate_h; const int jucer_PIPTemplate_hSize = 517; + extern const char* jucer_UnityPluginGUIScript_cs; + const int jucer_UnityPluginGUIScript_csSize = 6426; + extern const char* colourscheme_dark_xml; const int colourscheme_dark_xmlSize = 1050; @@ -192,7 +195,7 @@ namespace BinaryData const int RecentFilesMenuTemplate_nibSize = 2842; // Number of elements in the namedResourceList and originalFileNames arrays. - const int namedResourceListSize = 61; + const int namedResourceListSize = 62; // Points to the start of a list of resource names. extern const char* namedResourceList[]; diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index bc95b51087..98e27284f6 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -263,6 +263,8 @@ file="Source/BinaryData/Templates/jucer_PIPMain.cpp"/> + diff --git a/extras/Projucer/Source/BinaryData/Templates/jucer_UnityPluginGUIScript.cs b/extras/Projucer/Source/BinaryData/Templates/jucer_UnityPluginGUIScript.cs new file mode 100644 index 0000000000..1a47744a68 --- /dev/null +++ b/extras/Projucer/Source/BinaryData/Templates/jucer_UnityPluginGUIScript.cs @@ -0,0 +1,181 @@ +#if UNITY_EDITOR + +using UnityEditor; +using UnityEngine; + +using System.Collections.Generic; +using System.Runtime.InteropServices; + +public class %%plugin_name%%GUI : IAudioEffectPluginGUI +{ + public override string Name { get { return "%%plugin_name%%"; } } + public override string Description { get { return "%%plugin_description%%"; } } + public override string Vendor { get { return "%%plugin_vendor%%"; } } + + //============================================================================== + [DllImport("%%plugin_name%%")] static extern System.IntPtr getRenderCallback(); + + [DllImport("%%plugin_name%%")] static extern void unityInitialiseTexture (int id, System.IntPtr texture, int width, int height); + + [DllImport("%%plugin_name%%")] static extern void unityMouseDown (int id, float x, float y, EventModifiers mods, int button); + [DllImport("%%plugin_name%%")] static extern void unityMouseDrag (int id, float x, float y, EventModifiers mods, int button); + [DllImport("%%plugin_name%%")] static extern void unityMouseUp (int id, float x, float y, EventModifiers mods); + + [DllImport("%%plugin_name%%")] static extern void unityKeyEvent (int id, KeyCode code, EventModifiers mods, string name); + + [DllImport("%%plugin_name%%")] static extern void unitySetScreenBounds (int id, float x, float y, float w, float h); + + //============================================================================== + private class PluginGUIInstance + { + public PluginGUIInstance (ref IAudioEffectPlugin plugin, int id) + { + instanceID = id; + + float[] arr; + plugin.GetFloatBuffer ("Editor", out arr, 1); + hasEditor = (arr[0] > 0.0f); + } + + public void repaint (Rect r) + { + Vector2 newScreenPosition = GUIUtility.GUIToScreenPoint (r.position); + + if (bounds != r + || screenPosition != newScreenPosition) + { + screenPosition = newScreenPosition; + bounds = r; + + unitySetScreenBounds (instanceID, screenPosition.x, screenPosition.y, bounds.width, bounds.height); + setupTexture(); + } + + GL.IssuePluginEvent (getRenderCallback(), instanceID); + + texture.SetPixels32 (pixels); + texture.Apply(); + + EditorGUI.DrawPreviewTexture (bounds, texture); + } + + public bool handleMouseEvent (EventType eventType) + { + Vector2 mousePos = Event.current.mousePosition; + EventModifiers mods = Event.current.modifiers; + + if (! bounds.Contains (mousePos)) + return false; + + Vector2 relativePos = new Vector2 (mousePos.x - bounds.x, mousePos.y - bounds.y); + + if (eventType == EventType.MouseDown) + { + unityMouseDown (instanceID, relativePos.x, relativePos.y, mods, Event.current.button); + GUIUtility.hotControl = GUIUtility.GetControlID (FocusType.Passive); + } + else if (eventType == EventType.MouseUp) + { + unityMouseUp (instanceID, relativePos.x, relativePos.y, mods); + GUIUtility.hotControl = 0; + } + else if (eventType == EventType.MouseDrag) + { + unityMouseDrag (instanceID, relativePos.x, relativePos.y, mods, Event.current.button); + } + + Event.current.Use(); + + return true; + } + + public void handleKeyEvent (EventType eventType) + { + if (eventType == EventType.KeyDown) + { + KeyCode code = Event.current.keyCode; + + if (code == KeyCode.None) + return; + + EventModifiers mods = Event.current.modifiers; + + unityKeyEvent (instanceID, code, mods, code.ToString()); + } + } + + private void setupTexture() + { + if (pixelHandle.IsAllocated) + pixelHandle.Free(); + + texture = new Texture2D ((int) bounds.width, (int) bounds.height, TextureFormat.ARGB32, false); + + pixels = texture.GetPixels32(); + pixelHandle = GCHandle.Alloc (pixels, GCHandleType.Pinned); + + unityInitialiseTexture (instanceID, pixelHandle.AddrOfPinnedObject(), texture.width, texture.height); + } + + public int instanceID = -1; + public bool hasEditor; + + private Vector2 screenPosition; + private Rect bounds; + + private Texture2D texture; + private Color32[] pixels; + private GCHandle pixelHandle; + } + List guis = new List(); + + private PluginGUIInstance getGUIInstanceForPlugin (ref IAudioEffectPlugin plugin) + { + float[] idArray; + plugin.GetFloatBuffer ("ID", out idArray, 1); + + int id = (int) idArray[0]; + + for (int i = 0; i < guis.Count; ++i) + { + if (guis[i].instanceID == id) + return guis[i]; + } + + PluginGUIInstance newInstance = new PluginGUIInstance (ref plugin, id); + guis.Add (newInstance); + + return guis[guis.Count - 1]; + } + + //============================================================================== + public override bool OnGUI (IAudioEffectPlugin plugin) + { + PluginGUIInstance guiInstance = getGUIInstanceForPlugin (ref plugin); + + if (! guiInstance.hasEditor) + return true; + + float[] arr; + plugin.GetFloatBuffer ("Size", out arr, 6); + + Rect r = GUILayoutUtility.GetRect (arr[0], arr[1], + new GUILayoutOption[] { GUILayout.MinWidth (arr[2]), GUILayout.MinHeight (arr[3]), + GUILayout.MaxWidth (arr[4]), GUILayout.MaxHeight (arr[5]) }); + + int controlID = GUIUtility.GetControlID (FocusType.Passive); + Event currentEvent = Event.current; + EventType currentEventType = currentEvent.GetTypeForControl (controlID); + + if (currentEventType == EventType.Repaint) + guiInstance.repaint (r); + else if (currentEvent.isMouse) + guiInstance.handleMouseEvent (currentEventType); + else if (currentEvent.isKey) + guiInstance.handleKeyEvent (currentEventType); + + return false; + } +} + +#endif \ No newline at end of file diff --git a/extras/Projucer/Source/Project/jucer_Project.cpp b/extras/Projucer/Source/Project/jucer_Project.cpp index ded2c5f44a..a4076debae 100644 --- a/extras/Projucer/Source/Project/jucer_Project.cpp +++ b/extras/Projucer/Source/Project/jucer_Project.cpp @@ -822,6 +822,8 @@ bool Project::shouldBuildTargetType (ProjectType::Target::Type targetType) const return shouldBuildAUv3(); case ProjectType::Target::StandalonePlugIn: return shouldBuildStandalonePlugin(); + case ProjectType::Target::UnityPlugIn: + return shouldBuildUnityPlugin(); case ProjectType::Target::AggregateTarget: case ProjectType::Target::SharedCodeTarget: return projectType.isAudioPlugin(); @@ -843,6 +845,7 @@ ProjectType::Target::Type Project::getTargetTypeFromFilePath (const File& file, else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST2")) return ProjectType::Target::VSTPlugIn; else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST3")) return ProjectType::Target::VST3PlugIn; else if (LibraryModule::CompileUnit::hasSuffix (file, "_Standalone")) return ProjectType::Target::StandalonePlugIn; + else if (LibraryModule::CompileUnit::hasSuffix (file, "_Unity")) return ProjectType::Target::UnityPlugIn; return (returnSharedTargetIfNoValidSuffix ? ProjectType::Target::SharedCodeTarget : ProjectType::Target::unspecified); } @@ -862,6 +865,7 @@ const char* ProjectType::Target::getName() const noexcept case AudioUnitv3PlugIn: return "AUv3 AppExtension"; case AAXPlugIn: return "AAX"; case RTASPlugIn: return "RTAS"; + case UnityPlugIn: return "Unity Plugin"; case SharedCodeTarget: return "Shared Code"; case AggregateTarget: return "All"; default: return "undefined"; @@ -883,6 +887,7 @@ ProjectType::Target::TargetFileType ProjectType::Target::getTargetFileType() con case AudioUnitv3PlugIn: return macOSAppex; case AAXPlugIn: return pluginBundle; case RTASPlugIn: return pluginBundle; + case UnityPlugIn: return pluginBundle; case SharedCodeTarget: return staticLibrary; default: break; @@ -1016,9 +1021,10 @@ void Project::createPropertyEditors (PropertyListBuilder& props) void Project::createAudioPluginPropertyEditors (PropertyListBuilder& props) { props.add (new MultiChoicePropertyComponent (pluginFormatsValue, "Plugin Formats", - { "VST", "VST3", "AU", "AUv3", "RTAS", "AAX", "Standalone", "Enable IAA" }, + { "VST", "VST3", "AU", "AUv3", "RTAS", "AAX", "Standalone", "Unity", "Enable IAA" }, { Ids::buildVST.toString(), Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildAUv3.toString(), - Ids::buildRTAS.toString(), Ids::buildAAX.toString(), Ids::buildStandalone.toString(), Ids::enableIAA.toString() }), + Ids::buildRTAS.toString(), Ids::buildAAX.toString(), Ids::buildStandalone.toString(), Ids::buildUnity.toString(), + Ids::enableIAA.toString() }), "Plugin formats to build."); props.add (new MultiChoicePropertyComponent (pluginCharacteristicsValue, "Plugin Characteristics", { "Plugin is a Synth", "Plugin MIDI Input", "Plugin MIDI Output", "MIDI Effect Plugin", "Plugin Editor Requires Keyboard Focus", diff --git a/extras/Projucer/Source/Project/jucer_Project.h b/extras/Projucer/Source/Project/jucer_Project.h index 361f028d89..d827414843 100644 --- a/extras/Projucer/Source/Project/jucer_Project.h +++ b/extras/Projucer/Source/Project/jucer_Project.h @@ -158,6 +158,7 @@ public: bool shouldBuildRTAS() const { return checkMultiChoiceVar (pluginFormatsValue, Ids::buildRTAS); } bool shouldBuildAAX() const { return checkMultiChoiceVar (pluginFormatsValue, Ids::buildAAX); } bool shouldBuildStandalonePlugin() const { return checkMultiChoiceVar (pluginFormatsValue, Ids::buildStandalone); } + bool shouldBuildUnityPlugin() const { return checkMultiChoiceVar (pluginFormatsValue, Ids::buildUnity); } bool shouldEnableIAA() const { return checkMultiChoiceVar (pluginFormatsValue, Ids::enableIAA); } //============================================================================== @@ -199,6 +200,15 @@ public: String getIAATypeCode(); String getIAAPluginName(); + String getUnityScriptName() const { return addUnityPluginPrefixIfNecessary (getProjectNameString()) + "_UnityScript.cs"; } + static String addUnityPluginPrefixIfNecessary (const String& name) + { + if (! name.startsWithIgnoreCase ("audioplugin")) + return "audioplugin_" + name; + + return name; + } + //============================================================================== bool isAUPluginHost(); bool isVSTPluginHost(); diff --git a/extras/Projucer/Source/Project/jucer_ProjectType.h b/extras/Projucer/Source/Project/jucer_ProjectType.h index fadf4ead67..14c318a262 100644 --- a/extras/Projucer/Source/Project/jucer_ProjectType.h +++ b/extras/Projucer/Source/Project/jucer_ProjectType.h @@ -67,6 +67,7 @@ public: AudioUnitPlugIn = 14, AudioUnitv3PlugIn = 15, StandalonePlugIn = 16, + UnityPlugIn = 17, SharedCodeTarget = 20, // internal AggregateTarget = 21, @@ -186,6 +187,7 @@ struct ProjectType_AudioPlugin : public ProjectType case Target::AudioUnitPlugIn: case Target::AudioUnitv3PlugIn: case Target::StandalonePlugIn: + case Target::UnityPlugIn: case Target::SharedCodeTarget: case Target::AggregateTarget: return true; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index 941fc4b572..655ac08981 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -158,7 +158,8 @@ public: vstBinaryLocation (config, Ids::vstBinaryLocation, getUndoManager()), vst3BinaryLocation (config, Ids::vst3BinaryLocation, getUndoManager()), rtasBinaryLocation (config, Ids::rtasBinaryLocation, getUndoManager()), - aaxBinaryLocation (config, Ids::aaxBinaryLocation, getUndoManager()) + aaxBinaryLocation (config, Ids::aaxBinaryLocation, getUndoManager()), + unityPluginBinaryLocation (config, Ids::unityPluginBinaryLocation, getUndoManager(), {}) { if (! isDebug()) updateOldLTOSetting(); @@ -203,6 +204,7 @@ public: String getVST3BinaryLocationString() const { return vst3BinaryLocation.get(); } String getRTASBinaryLocationString() const { return rtasBinaryLocation.get();} String getAAXBinaryLocationString() const { return aaxBinaryLocation.get();} + String getUnityPluginBinaryLocationString() const { return unityPluginBinaryLocation.get(); } //============================================================================== String createMSVCConfigName() const @@ -210,9 +212,9 @@ public: return getName() + "|" + (is64Bit() ? "x64" : "Win32"); } - String getOutputFilename (const String& suffix, bool forceSuffix) const + String getOutputFilename (const String& suffix, bool forceSuffix, bool forceUnityPrefix) const { - auto target = File::createLegalFileName (getTargetBinaryNameString().trim()); + auto target = File::createLegalFileName (getTargetBinaryNameString (forceUnityPrefix).trim()); if (forceSuffix || ! target.containsChar ('.')) return target.upToLastOccurrenceOf (".", false, false) + suffix; @@ -308,7 +310,7 @@ public: generateManifestValue, enableIncrementalLinkingValue, useRuntimeLibDLLValue, intermediatesPathValue, characterSetValue, architectureTypeValue, fastMathValue, debugInformationFormatValue, pluginBinaryCopyStepValue; - ValueWithDefault vstBinaryLocation, vst3BinaryLocation, rtasBinaryLocation, aaxBinaryLocation; + ValueWithDefault vstBinaryLocation, vst3BinaryLocation, rtasBinaryLocation, aaxBinaryLocation, unityPluginBinaryLocation; Value architectureValueToListenTo; @@ -321,8 +323,8 @@ public: void addVisualStudioPluginInstallPathProperties (PropertyListBuilder& props) { - auto isBuildingAnyPlugins = (project.shouldBuildVST() || project.shouldBuildVST3() - || project.shouldBuildRTAS() || project.shouldBuildAAX()); + auto isBuildingAnyPlugins = (project.shouldBuildVST() || project.shouldBuildVST3() || project.shouldBuildRTAS() + || project.shouldBuildAAX() || project.shouldBuildUnityPlugin()); if (isBuildingAnyPlugins) props.add (new ChoicePropertyComponent (pluginBinaryCopyStepValue, "Enable Plugin Copy Step"), @@ -348,6 +350,11 @@ public: 1024, false), "The folder in which the compiled AAX binary should be placed."); + if (project.shouldBuildUnityPlugin()) + props.add (new TextPropertyComponentWithEnablement (unityPluginBinaryLocation, pluginBinaryCopyStepValue, "Unity Binary Location", + 1024, false), + "The folder in which the compiled Unity plugin binary and associated C# GUI script should be placed."); + } void setPluginBinaryCopyLocationDefaults() @@ -497,7 +504,7 @@ public: { auto* targetName = props->createNewChildElement ("TargetName"); setConditionAttribute (*targetName, config); - targetName->addTextElement (config.getOutputFilename ("", false)); + targetName->addTextElement (config.getOutputFilename ("", false, type == UnityPlugIn)); } { @@ -608,12 +615,12 @@ public: { auto* link = group->createNewChildElement ("Link"); - link->createNewChildElement ("OutputFile")->addTextElement (getOutputFilePath (config)); + link->createNewChildElement ("OutputFile")->addTextElement (getOutputFilePath (config, type == UnityPlugIn)); link->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); link->createNewChildElement ("IgnoreSpecificDefaultLibraries")->addTextElement (isDebug ? "libcmt.lib; msvcrt.lib;;%(IgnoreSpecificDefaultLibraries)" : "%(IgnoreSpecificDefaultLibraries)"); link->createNewChildElement ("GenerateDebugInformation")->addTextElement ((isDebug || config.shouldGenerateDebugSymbols()) ? "true" : "false"); - link->createNewChildElement ("ProgramDatabaseFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".pdb", true))); + link->createNewChildElement ("ProgramDatabaseFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".pdb", true, type == UnityPlugIn))); link->createNewChildElement ("SubSystem")->addTextElement (type == ConsoleApp ? "Console" : "Windows"); if (! config.is64Bit()) @@ -654,7 +661,7 @@ public: { auto* bsc = group->createNewChildElement ("Bscmake"); bsc->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); - bsc->createNewChildElement ("OutputFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".bsc", true))); + bsc->createNewChildElement ("OutputFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".bsc", true, type == UnityPlugIn))); } if (type != SharedCodeTarget) @@ -1078,13 +1085,13 @@ public: RelativePath bundleScript = aaxSDK.getChildFile ("Utilities").getChildFile ("CreatePackage.bat"); RelativePath iconFilePath = getAAXIconFile(); - auto outputFilename = config.getOutputFilename (".aaxplugin", true); + auto outputFilename = config.getOutputFilename (".aaxplugin", true, false); auto bundleDir = getOwner().getOutDirFile (config, outputFilename); auto bundleContents = bundleDir + "\\Contents"; auto archDir = bundleContents + String ("\\") + (config.is64Bit() ? "x64" : "Win32"); auto executable = archDir + String ("\\") + outputFilename; - auto pkgScript = String ("copy /Y ") + getOutputFilePath (config).quoted() + String (" ") + executable.quoted() + String ("\r\ncall ") + auto pkgScript = String ("copy /Y ") + getOutputFilePath (config, false).quoted() + String (" ") + executable.quoted() + String ("\r\ncall ") + createRebasedPath (bundleScript) + String (" ") + archDir.quoted() + String (" ") + createRebasedPath (iconFilePath); if (config.isPluginBinaryCopyStepEnabled()) @@ -1093,6 +1100,24 @@ public: return pkgScript; } + else if (type == UnityPlugIn) + { + RelativePath scriptPath (config.project.getGeneratedCodeFolder().getChildFile (config.project.getUnityScriptName()), + getOwner().getTargetFolder(), + RelativePath::projectFolder); + + auto pkgScript = String ("copy /Y ") + scriptPath.toWindowsStyle().quoted() + " \"$(OutDir)\""; + + if (config.isPluginBinaryCopyStepEnabled()) + { + auto copyLocation = config.getUnityPluginBinaryLocationString(); + + pkgScript += "\r\ncopy /Y \"$(OutDir)$(TargetFileName)\" " + String (copyLocation + "\\$(TargetFileName)").quoted(); + pkgScript += "\r\ncopy /Y " + String ("$(OutDir)" + config.project.getUnityScriptName()).quoted() + " " + String (copyLocation + "\\" + config.project.getUnityScriptName()).quoted(); + } + + return pkgScript; + } else if (config.isPluginBinaryCopyStepEnabled()) { auto copyScript = String ("copy /Y \"$(OutDir)$(TargetFileName)\"") + String (" \"$COPYDIR$\\$(TargetFileName)\""); @@ -1111,7 +1136,7 @@ public: { String script; - auto bundleDir = getOwner().getOutDirFile (config, config.getOutputFilename (".aaxplugin", false)); + auto bundleDir = getOwner().getOutDirFile (config, config.getOutputFilename (".aaxplugin", false, false)); auto bundleContents = bundleDir + "\\Contents"; auto archDir = bundleContents + String ("\\") + (config.is64Bit() ? "x64" : "Win32"); @@ -1210,14 +1235,14 @@ public: return searchPaths; } - String getBinaryNameWithSuffix (const MSVCBuildConfiguration& config) const + String getBinaryNameWithSuffix (const MSVCBuildConfiguration& config, bool forceUnityPrefix) const { - return config.getOutputFilename (getTargetSuffix(), true); + return config.getOutputFilename (getTargetSuffix(), true, forceUnityPrefix); } - String getOutputFilePath (const MSVCBuildConfiguration& config) const + String getOutputFilePath (const MSVCBuildConfiguration& config, bool forceUnityPrefix) const { - return getOwner().getOutDirFile (config, getBinaryNameWithSuffix (config)); + return getOwner().getOutDirFile (config, getBinaryNameWithSuffix (config, forceUnityPrefix)); } StringArray getLibrarySearchPaths (const BuildConfiguration& config) const @@ -1244,7 +1269,7 @@ public: if (type != SharedCodeTarget) if (auto* shared = getOwner().getSharedCodeTarget()) - libraries.add (shared->getBinaryNameWithSuffix (config)); + libraries.add (shared->getBinaryNameWithSuffix (config, false)); return libraries.joinIntoString (";"); } @@ -1325,6 +1350,7 @@ public: case ProjectType::Target::VST3PlugIn: case ProjectType::Target::AAXPlugIn: case ProjectType::Target::RTASPlugIn: + case ProjectType::Target::UnityPlugIn: case ProjectType::Target::DynamicLibrary: return true; default: @@ -1344,8 +1370,6 @@ public: } //============================================================================== - const String& getProjectName() const { return projectName; } - bool launchProject() override { #if JUCE_WINDOWS diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h index 3ea5f6bce9..b6ad1b384e 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h @@ -179,6 +179,7 @@ public: switch (type) { case VSTPlugIn: + case UnityPlugIn: case DynamicLibrary: return ".so"; case SharedCodeTarget: case StaticLibrary: return ".a"; @@ -362,6 +363,7 @@ public: case ProjectType::Target::VSTPlugIn: case ProjectType::Target::StandalonePlugIn: case ProjectType::Target::DynamicLibrary: + case ProjectType::Target::UnityPlugIn: return true; default: break; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h index 26f767b161..4897906803 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h @@ -179,6 +179,7 @@ public: case ProjectType::Target::RTASPlugIn: case ProjectType::Target::AudioUnitPlugIn: case ProjectType::Target::DynamicLibrary: + case ProjectType::Target::UnityPlugIn: return ! iOS; default: break; @@ -442,7 +443,8 @@ protected: vst3BinaryLocation (config, Ids::vst3BinaryLocation, getUndoManager(), "$(HOME)/Library/Audio/Plug-Ins/VST3/"), auBinaryLocation (config, Ids::auBinaryLocation, getUndoManager(), "$(HOME)/Library/Audio/Plug-Ins/Components/"), rtasBinaryLocation (config, Ids::rtasBinaryLocation, getUndoManager(), "/Library/Application Support/Digidesign/Plug-Ins/"), - aaxBinaryLocation (config, Ids::aaxBinaryLocation, getUndoManager(), "/Library/Application Support/Avid/Audio/Plug-Ins/") + aaxBinaryLocation (config, Ids::aaxBinaryLocation, getUndoManager(), "/Library/Application Support/Avid/Audio/Plug-Ins/"), + unityPluginBinaryLocation (config, Ids::unityPluginBinaryLocation, getUndoManager()) { updateOldPluginBinaryLocations(); updateOldSDKDefaults(); @@ -539,6 +541,7 @@ protected: String getAUBinaryLocationString() const { return auBinaryLocation.get(); } String getRTASBinaryLocationString() const { return rtasBinaryLocation.get();} String getAAXBinaryLocationString() const { return aaxBinaryLocation.get();} + String getUnityPluginBinaryLocationString() const { return unityPluginBinaryLocation.get(); } private: //========================================================================== @@ -547,7 +550,8 @@ protected: ValueWithDefault osxSDKVersion, osxDeploymentTarget, iosDeploymentTarget, osxArchitecture, customXcodeFlags, plistPreprocessorDefinitions, codeSignIdentity, fastMathEnabled, stripLocalSymbolsEnabled, pluginBinaryCopyStepEnabled, - vstBinaryLocation, vst3BinaryLocation, auBinaryLocation, rtasBinaryLocation, aaxBinaryLocation; + vstBinaryLocation, vst3BinaryLocation, auBinaryLocation, rtasBinaryLocation, + aaxBinaryLocation, unityPluginBinaryLocation; //========================================================================== void addXcodePluginInstallPathProperties (PropertyListBuilder& props) @@ -583,6 +587,11 @@ protected: props.add (new TextPropertyComponentWithEnablement (aaxBinaryLocation, pluginBinaryCopyStepEnabled, "AAX Binary Location", 1024, false), "The folder in which the compiled AAX binary should be placed."); + + if (project.shouldBuildUnityPlugin()) + props.add (new TextPropertyComponentWithEnablement (unityPluginBinaryLocation, pluginBinaryCopyStepEnabled, "Unity Binary Location", + 1024, false), + "The folder in which the compiled Unity plugin binary and associated C# GUI script should be placed."); } void updateOldPluginBinaryLocations() @@ -734,6 +743,15 @@ public: xcodeCopyToProductInstallPathAfterBuild = true; break; + case UnityPlugIn: + xcodePackageType = "BNDL"; + xcodeBundleSignature = "????"; + xcodeFileType = "wrapper.cfbundle"; + xcodeBundleExtension = ".bundle"; + xcodeProductType = "com.apple.product-type.bundle"; + xcodeCopyToProductInstallPathAfterBuild = true; + break; + case SharedCodeTarget: xcodeFileType = "archive.ar"; xcodeBundleExtension = ".a"; @@ -822,7 +840,7 @@ public: if (ProjectExporter::BuildConfiguration::Ptr config = owner.getConfiguration(0)) { - auto productName = owner.replacePreprocessorTokens (*config, config->getTargetBinaryNameString()); + auto productName = owner.replacePreprocessorTokens (*config, config->getTargetBinaryNameString (type == UnityPlugIn)); if (xcodeFileType == "archive.ar") productName = getStaticLibbedFilename (productName); @@ -995,6 +1013,7 @@ public: return s; } + s.set ("PRODUCT_NAME", owner.replacePreprocessorTokens (config, config.getTargetBinaryNameString (type == UnityPlugIn)).quoted()); s.set ("PRODUCT_BUNDLE_IDENTIFIER", getBundleIdentifier()); auto arch = (! owner.isiOS() && type == Target::AudioUnitv3PlugIn) ? osxArch_64Bit @@ -1231,6 +1250,7 @@ public: case AudioUnitPlugIn: return config.isPluginBinaryCopyStepEnabled() ? config.getAUBinaryLocationString() : String(); case RTASPlugIn: return config.isPluginBinaryCopyStepEnabled() ? config.getRTASBinaryLocationString() : String(); case AAXPlugIn: return config.isPluginBinaryCopyStepEnabled() ? config.getAAXBinaryLocationString() : String(); + case UnityPlugIn: return config.isPluginBinaryCopyStepEnabled() ? config.getUnityPluginBinaryLocationString() : String(); case SharedCodeTarget: return owner.isiOS() ? "@executable_path/Frameworks" : "@executable_path/../Frameworks"; default: return {}; } @@ -1927,6 +1947,10 @@ private: && project.shouldBuildStandalonePlugin() && target->type == XcodeTarget::StandalonePlugIn) embedAppExtension(); + if (project.getProjectType().isAudioPlugin() && project.shouldBuildUnityPlugin() + && target->type == XcodeTarget::UnityPlugIn) + embedUnityScript(); + addTargetObject (*target); } } @@ -1944,6 +1968,25 @@ private: } } + void embedUnityScript() const + { + if (auto* unityTarget = getTargetOfType (XcodeTarget::UnityPlugIn)) + { + RelativePath scriptPath (getProject().getGeneratedCodeFolder().getChildFile (getProject().getUnityScriptName()), + getTargetFolder(), + RelativePath::buildTargetFolder); + + auto path = scriptPath.toUnixStyle(); + auto refID = addFileReference (path); + auto fileID = addBuildFile (path, refID, false, false); + + resourceIDs.add (fileID); + resourceFileRefs.add (refID); + + unityTarget->addCopyFilesPhase ("Embed Unity Script", fileID, kWrapperFolder); + } + } + static Image fixMacIconImageSize (Drawable& image) { const int validSizes[] = { 16, 32, 48, 128, 256, 512, 1024 }; @@ -1992,6 +2035,7 @@ private: v->setProperty ("dependencies", indentParenthesisedList (getTargetDependencies (target)), nullptr); v->setProperty (Ids::name, target.getXcodeSchemeName(), nullptr); + v->setProperty ("productName", projectName, nullptr); if (target.type != XcodeTarget::AggregateTarget) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp index b0b07b98f4..655970c362 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp @@ -450,7 +450,8 @@ void ProjectExporter::addTargetSpecificPreprocessorDefs (StringPairArray& defs, {"JucePlugin_Build_AUv3", ProjectType::Target::AudioUnitv3PlugIn}, {"JucePlugin_Build_RTAS", ProjectType::Target::RTASPlugIn}, {"JucePlugin_Build_AAX", ProjectType::Target::AAXPlugIn}, - {"JucePlugin_Build_Standalone", ProjectType::Target::StandalonePlugIn} + {"JucePlugin_Build_Standalone", ProjectType::Target::StandalonePlugIn}, + {"JucePlugin_Build_Unity", ProjectType::Target::UnityPlugIn} }; if (targetType == ProjectType::Target::SharedCodeTarget) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h index 07cec42835..a42f94a50e 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h @@ -244,8 +244,12 @@ public: String getName() const { return configNameValue.get(); } bool isDebug() const { return isDebugValue.get(); } - String getTargetBinaryNameString() const { return targetNameValue.get(); } String getTargetBinaryRelativePathString() const { return targetBinaryPathValue.get(); } + String getTargetBinaryNameString (bool isUnityPlugin = false) const + { + return (isUnityPlugin ? Project::addUnityPluginPrefixIfNecessary (targetNameValue.get().toString()) + : targetNameValue.get().toString()); + } int getOptimisationLevelInt() const { return optimisationLevelValue.get(); } String getGCCOptimisationFlag() const; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp index 4d8d9a4830..da1459d6fc 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp @@ -83,6 +83,7 @@ void ProjectSaver::writePluginCharacteristicsFile() flags.set ("JucePlugin_Build_RTAS", boolToString (project.shouldBuildRTAS())); flags.set ("JucePlugin_Build_AAX", boolToString (project.shouldBuildAAX())); flags.set ("JucePlugin_Build_Standalone", boolToString (project.shouldBuildStandalonePlugin())); + flags.set ("JucePlugin_Build_Unity", boolToString (project.shouldBuildUnityPlugin())); flags.set ("JucePlugin_Enable_IAA", boolToString (project.shouldEnableIAA())); flags.set ("JucePlugin_Name", toStringLiteral (project.getPluginNameString())); flags.set ("JucePlugin_Desc", toStringLiteral (project.getPluginDescriptionString())); diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h index 7a3a2a4b53..cd220c66eb 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h @@ -94,8 +94,13 @@ public: auto projectRootHash = project.getProjectRoot().toXmlString().hashCode(); if (project.getProjectType().isAudioPlugin()) + { writePluginCharacteristicsFile(); + if (project.shouldBuildUnityPlugin()) + writeUnityScriptFile(); + } + writeAppConfigFile (modules, appConfigUserContent); writeBinaryDataFiles(); writeAppHeader (modules); @@ -669,6 +674,24 @@ private: void writePluginCharacteristicsFile(); + void writeUnityScriptFile() + { + String unityScriptContents (BinaryData::jucer_UnityPluginGUIScript_cs); + + auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString()); + + unityScriptContents = unityScriptContents.replace ("%%plugin_name%%", projectName) + .replace ("%%plugin_vendor%%", project.getPluginManufacturerString()) + .replace ("%%plugin_description%%", project.getPluginDescriptionString()); + + auto f = getGeneratedCodeFolder().getChildFile (project.getUnityScriptName()); + + MemoryOutputStream out; + out << unityScriptContents; + + replaceFileIfDifferent (f, out); + } + void writeProjects (const OwnedArray&, const String&, bool); void saveExporter (ProjectExporter* exporter, const OwnedArray& modules) diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h index 7b66a4695e..d0ec414253 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h @@ -118,6 +118,7 @@ namespace Ids DECLARE_ID (auBinaryLocation); DECLARE_ID (rtasBinaryLocation); DECLARE_ID (aaxBinaryLocation); + DECLARE_ID (unityPluginBinaryLocation); DECLARE_ID (enablePluginBinaryCopyStep); DECLARE_ID (stripLocalSymbols); DECLARE_ID (osxSDK); @@ -298,6 +299,7 @@ namespace Ids DECLARE_ID (buildRTAS); DECLARE_ID (buildAAX); DECLARE_ID (buildStandalone); + DECLARE_ID (buildUnity); DECLARE_ID (enableIAA); DECLARE_ID (pluginName); DECLARE_ID (pluginDesc); diff --git a/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h b/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h new file mode 100644 index 0000000000..1778d05772 --- /dev/null +++ b/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h @@ -0,0 +1,191 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + + +//============================================================================== +#define UNITY_AUDIO_PLUGIN_API_VERSION 0x010401 + +#if JUCE_WINDOWS + #define UNITY_INTERFACE_API __stdcall + #define UNITY_INTERFACE_EXPORT __declspec(dllexport) +#else + #define UNITY_INTERFACE_API + #define UNITY_INTERFACE_EXPORT __attribute__ ((visibility("default"))) +#endif + +//============================================================================== +struct UnityAudioEffectState; + +typedef int (UNITY_INTERFACE_API * createCallback) (UnityAudioEffectState* state); +typedef int (UNITY_INTERFACE_API * releaseCallback) (UnityAudioEffectState* state); +typedef int (UNITY_INTERFACE_API * resetCallback) (UnityAudioEffectState* state); + +typedef int (UNITY_INTERFACE_API * processCallback) (UnityAudioEffectState* state, float* inBuffer, float* outBuffer, unsigned int bufferSize, + int numInChannels, int numOutChannels); + +typedef int (UNITY_INTERFACE_API * setPositionCallback) (UnityAudioEffectState* state, unsigned int pos); + +typedef int (UNITY_INTERFACE_API * setFloatParameterCallback) (UnityAudioEffectState* state, int index, float value); +typedef int (UNITY_INTERFACE_API * getFloatParameterCallback) (UnityAudioEffectState* state, int index, float* value, char* valuestr); +typedef int (UNITY_INTERFACE_API * getFloatBufferCallback) (UnityAudioEffectState* state, const char* name, float* buffer, int numsamples); + +typedef int (UNITY_INTERFACE_API * distanceAttenuationCallback) (UnityAudioEffectState* state, float distanceIn, float attenuationIn, float* attenuationOut); + +typedef void (UNITY_INTERFACE_API * renderCallback) (int eventId); + +//============================================================================== +enum UnityAudioEffectDefinitionFlags +{ + isSideChainTarget = 1, + isSpatializer = 2, + isAmbisonicDecoder = 4, + appliesDistanceAttenuation = 8 +}; + +enum UnityAudioEffectStateFlags +{ + stateIsPlaying = 1, + stateIsPaused = 2, + stateIsMuted = 8, + statIsSideChainTarget = 16 +}; + +enum UnityEventModifiers +{ + shift = 1, + control = 2, + alt = 4, + command = 8, + numeric = 16, + capsLock = 32, + functionKey = 64 +}; + +//============================================================================== +struct UnityAudioSpatializerData +{ + float listenerMatrix[16]; + float sourceMatrix[16]; + float spatialBlend; + float reverbZoneMix; + float spread; + float stereoPan; + distanceAttenuationCallback distanceAttenuationCallback; + float minDistance; + float maxDistance; +}; + +struct UnityAudioAmbisonicData +{ + float listenerMatrix[16]; + float sourceMatrix[16]; + float spatialBlend; + float reverbZoneMix; + float spread; + float stereoPan; + distanceAttenuationCallback distanceAttenuationCallback; + int ambisonicOutChannels; + float volume; +}; + +struct UnityAudioEffectState +{ + juce::uint32 structSize; + juce::uint32 sampleRate; + juce::uint64 dspCurrentTick; + juce::uint64 dspPreviousTick; + float* sidechainBuffer; + void* effectData; + juce::uint32 flags; + void* internal; + + UnityAudioSpatializerData* spatializerData; + juce::uint32 dspBufferSize; + juce::uint32 hostAPIVersion; + + UnityAudioAmbisonicData* ambisonicData; + + template + inline T* getEffectData() const + { + jassert (effectData != nullptr); + jassert (internal != nullptr); + + return (T*) effectData; + } +}; + +struct UnityAudioParameterDefinition +{ + char name[16]; + char unit[16]; + const char* description; + float min; + float max; + float defaultVal; + float displayScale; + float displayExponent; +}; + +struct UnityAudioEffectDefinition +{ + juce::uint32 structSize; + juce::uint32 parameterStructSize; + juce::uint32 apiVersion; + juce::uint32 pluginVersion; + juce::uint32 channels; + juce::uint32 numParameters; + juce::uint64 flags; + char name[32]; + createCallback create; + releaseCallback release; + resetCallback reset; + processCallback process; + setPositionCallback setPosition; + UnityAudioParameterDefinition* parameterDefintions; + setFloatParameterCallback setFloatParameter; + getFloatParameterCallback getFloatParameter; + getFloatBufferCallback getFloatBuffer; +}; + +//============================================================================== +// Unity callback +extern "C" UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr); + +// GUI script callbacks +extern "C" UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback(); + +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* textureHandle, int w, int h); + +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers mods, int button); +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers mods, int button); +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers mods); + +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name); + +extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h); diff --git a/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp b/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp new file mode 100644 index 0000000000..7722f4e6c0 --- /dev/null +++ b/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp @@ -0,0 +1,779 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#if JucePlugin_Build_Unity + +#include "../../juce_core/system/juce_TargetPlatform.h" +#include "../utility/juce_IncludeModuleHeaders.h" +#include "../../juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp" + +#if JUCE_WINDOWS + #include "../utility/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 getBounds() const override { return bounds; } + Point localToGlobal (Point relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } + Point globalToLocal (Point screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } + + StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } + + void setBounds (const Rectangle& newBounds, bool) override + { + bounds = newBounds; + mouseWatcher.setBoundsToWatch (bounds); + } + + bool contains (Point 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 (&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 position, ModifierKeys mods) + { + ModifierKeys::currentModifiers = mods; + + handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::invalidPressure, + juce::MouseInputSource::invalidOrientation, 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) + { + } + + ImageType* createType() const override { return new SoftwareImageType(); } + LowLevelGraphicsContext* createLowLevelContext() override { return new LowLevelGraphicsSoftwareRenderer (Image (this)); } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override + { + ignoreUnused (mode); + + bitmap.data = imageData + x * pixelStride + y * lineStride; + 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::invalidPressure, juce::MouseInputSource::invalidOrientation, juce::Time::currentTimeMillis()); + + lastMousePos = pos; + } + + } + + void setBoundsToWatch (Rectangle b) + { + if (boundsToWatch != b) + boundsToWatch = b; + + startTimer (250); + } + + ComponentPeer& owner; + Rectangle boundsToWatch; + Point 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 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; } + BorderSize getFrameSize() const override { return {}; } + void setVisible (bool) override {} + void setTitle (const String&) override {} + void setIcon (const Image&) override {} + void textInputRequired (Point, TextInputTarget&) override {} + void setAlpha (float) override {} + void performAnyPendingRepaintsNow() override {} + void repaint (const Rectangle&) override {} + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) +}; + +ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } + +//============================================================================== +class AudioProcessorUnityWrapper +{ +public: + AudioProcessorUnityWrapper (bool isTemporary) + { + pluginInstance.reset (createPluginFilterOfType (AudioProcessor::wrapperType_Unity)); + + if (! isTemporary && pluginInstance->hasEditor()) + { + pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); + pluginInstanceEditor->setVisible (true); + pluginInstanceEditor->addToDesktop (0); + } + + 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 = state->dspBufferSize; + + pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); + 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, unsigned int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + for (int pos = 0; pos < static_cast (bufferSize);) + { + auto max = jmin (static_cast (bufferSize) - pos, samplesPerBlock); + processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); + + pos += max; + } + } + + void declareParameters (UnityAudioEffectDefinition& definition) + { + static std::unique_ptr parametersPtr; + static int numParams = 0; + + if (parametersPtr == nullptr) + { + auto paramsCopy = juceParameters.params; + for (auto* parameter : paramsCopy) + { + // Unity only displays parameters using a slider so remove any choice parameters + if (! parameter->getAllValueStrings().isEmpty()) + paramsCopy.remove (paramsCopy.indexOf (parameter)); + } + + numParams = paramsCopy.size(); + + parametersPtr.reset (static_cast (std::calloc (static_cast (numParams), + sizeof (UnityAudioParameterDefinition)))); + + parameterDescriptions.clear(); + + for (int i = 0; i < numParams; ++i) + { + auto* parameter = paramsCopy[i]; + auto& paramDef = parametersPtr.get()[i]; + + strncpy (paramDef.name, parameter->getName (15).toRawUTF8(), 15); + + if (parameter->getLabel().isNotEmpty()) + strncpy (paramDef.unit, parameter->getLabel().toRawUTF8(), 15); + + parameterDescriptions.add (parameter->getName (15)); + paramDef.description = parameterDescriptions[i].toRawUTF8(); + + paramDef.defaultVal = parameter->getDefaultValue(); + paramDef.min = 0.0f; + paramDef.max = 1.0f; + + float scale = 1.0f; + float exp = 1.0f; + + if (auto* floatParam = dynamic_cast (parameter)) + { + scale = floatParam->range.end; + exp = floatParam->range.skew; + } + else if (auto* intParam = dynamic_cast (parameter)) + { + scale = (float) intParam->getRange().getEnd(); + } + + paramDef.displayScale = scale; + paramDef.displayExponent = exp; + } + } + + definition.numParameters = numParams; + definition.parameterDefintions = parametersPtr.get(); + } + + void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValue (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 (pluginInstanceEditor->getPeer()); + + jassert (peer != nullptr); + return *peer; + } + +private: + //============================================================================== + void processBuffers (float* inBuffer, float* outBuffer, unsigned int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + int ch; + for (ch = 0; ch < numInChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + 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->processBlockBypassed (scratchBuffer, mb); + else + pluginInstance->processBlock (scratchBuffer, mb); + } + } + + for (ch = 0; ch < numOutChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + DstSampleType dstData (outBuffer + ch, numOutChannels); + SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); + dstData.convertSamples (srcData, bufferSize); + } + } + + //============================================================================== + std::unique_ptr pluginInstance; + std::unique_ptr pluginInstanceEditor; + + int samplesPerBlock = 1024; + StringArray parameterDescriptions; + + AudioBuffer scratchBuffer; + + LegacyAudioParametersWrapper juceParameters; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) +}; + +//============================================================================== +HashMap& getWrapperMap() +{ + static HashMap 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); +} + +//============================================================================== +namespace UnityCallbacks +{ + int UNITY_INTERFACE_API createCallback (UnityAudioEffectState* state) + { + if (getWrapperMap().size() == 0) + initialiseJuce_GUI(); + + auto* pluginInstance = new AudioProcessorUnityWrapper (false); + pluginInstance->create (state); + + state->effectData = pluginInstance; + + onWrapperCreation (pluginInstance); + + return 0; + } + + int UNITY_INTERFACE_API releaseCallback (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->release(); + + onWrapperDeletion (pluginInstance); + delete pluginInstance; + + if (getWrapperMap().size() == 0) + shutdownJuce_GUI(); + + return 0; + } + + int UNITY_INTERFACE_API resetCallback (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->reset(); + + return 0; + } + + int UNITY_INTERFACE_API setPositionCallback (UnityAudioEffectState* state, unsigned int pos) + { + ignoreUnused (state, pos); + + return 0; + } + + int UNITY_INTERFACE_API setFloatParameterCallback (UnityAudioEffectState* state, int index, float value) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->setParameter (index, value); + + return 0; + } + + int UNITY_INTERFACE_API getFloatParameterCallback (UnityAudioEffectState* state, int index, float* value, char* valueStr) + { + auto* pluginInstance = state->getEffectData(); + *value = pluginInstance->getParameter (index); + + strncpy (valueStr, pluginInstance->getParameterString (index).toRawUTF8(), 15); + + return 0; + } + + int UNITY_INTERFACE_API getFloatBufferCallback (UnityAudioEffectState* state, const char* name, float* buffer, int numSamples) + { + ignoreUnused (numSamples); + + auto nameStr = String (name); + + if (nameStr == "Editor") + { + auto* pluginInstance = state->getEffectData(); + + buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; + } + else if (nameStr == "ID") + { + auto* pluginInstance = state->getEffectData(); + + for (HashMap::Iterator i (getWrapperMap()); i.next();) + { + if (i.getValue() == pluginInstance) + { + buffer[0] = (float) i.getKey(); + break; + } + } + + return 0; + } + else if (nameStr == "Size") + { + auto* pluginInstance = state->getEffectData(); + + 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; + } + + int UNITY_INTERFACE_API processCallback (UnityAudioEffectState* state, float* inBuffer, float* outBuffer, + unsigned int bufferSize, int numInChannels, int numOutChannels) + { + auto* pluginInstance = state->getEffectData(); + + if (pluginInstance != nullptr) + { + auto isPlaying = ((state->flags & stateIsPlaying) != 0); + auto isMuted = ((state->flags & stateIsMuted) != 0); + auto isPaused = ((state->flags & stateIsPaused) != 0); + + auto bypassed = ! isPlaying || (isMuted || isPaused); + + pluginInstance->process (inBuffer, outBuffer, bufferSize, numInChannels, numOutChannels, bypassed); + } + else + { + FloatVectorOperations::clear (outBuffer, bufferSize * numOutChannels); + } + + return 0; + } +} + +//============================================================================== +static void declareEffect (UnityAudioEffectDefinition& definition) +{ + memset (&definition, 0, sizeof (definition)); + + std::unique_ptr wrapper = std::make_unique (true); + + String name (JucePlugin_Name); + if (! name.startsWithIgnoreCase ("audioplugin")) + name = "audioplugin_" + name; + + strcpy (definition.name, name.toRawUTF8()); + + definition.structSize = sizeof (UnityAudioEffectDefinition); + definition.parameterStructSize = sizeof (UnityAudioParameterDefinition); + + definition.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; + definition.pluginVersion = JucePlugin_VersionCode; + + // effects must set this to 0, generators > 0 + definition.channels = (wrapper->getNumInputChannels() != 0 ? 0 : wrapper->getNumOutputChannels()); + + wrapper->declareParameters (definition); + + definition.create = UnityCallbacks::createCallback; + definition.release = UnityCallbacks::releaseCallback; + definition.reset = UnityCallbacks::resetCallback; + definition.setPosition = UnityCallbacks::setPositionCallback; + definition.process = UnityCallbacks::processCallback; + definition.setFloatParameter = UnityCallbacks::setFloatParameterCallback; + definition.getFloatParameter = UnityCallbacks::getFloatParameterCallback; + definition.getFloatBuffer = UnityCallbacks::getFloatBufferCallback; +} + +} // namespace juce + +UNITY_INTERFACE_EXPORT int UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) +{ + static bool hasSetWrapperType = false; + + if (! hasSetWrapperType) + { + juce::PluginHostType::jucePlugInClientCurrentWrapperType = juce::AudioProcessor::wrapperType_Unity; + juce::juce_createUnityPeerFn = juce::createUnityPeer; + + hasSetWrapperType = true; + } + + auto* definition = new UnityAudioEffectDefinition(); + juce::declareEffect (*definition); + + *definitionsPtr = &definition; + + 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 unityInitialiseTexture (int id, void* data, int w, int h) +{ + getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast (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 + extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) + { + if (reason == DLL_PROCESS_ATTACH) + juce::Process::setCurrentModuleInstanceHandle (instance); + + return true; + } +#endif + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp new file mode 100644 index 0000000000..c0b31a60f3 --- /dev/null +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp @@ -0,0 +1,27 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "Unity/juce_Unity_Wrapper.cpp" diff --git a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h index ca388ee5ce..f409687461 100644 --- a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h +++ b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h @@ -30,7 +30,8 @@ #if ! (JucePlugin_Build_VST || JucePlugin_Build_VST3 \ || JucePlugin_Build_AU || JucePlugin_Build_AUv3 \ ||JucePlugin_Build_RTAS || JucePlugin_Build_AAX \ - || JucePlugin_Build_Standalone || JucePlugin_Build_LV2) + || JucePlugin_Build_Standalone || JucePlugin_Build_LV2 \ + || JucePlugin_Build_Unity) #error "You need to enable at least one plugin format!" #endif diff --git a/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp b/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp index 831f9ec4b5..9b5c214f06 100644 --- a/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp +++ b/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp @@ -39,6 +39,10 @@ namespace juce AudioProcessor::WrapperType PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_Undefined; +#if JucePlugin_Build_Unity + bool juce_isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } +#endif + #ifndef JUCE_VST3_CAN_REPLACE_VST2 #define JUCE_VST3_CAN_REPLACE_VST2 1 #endif diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 201f3b3636..01b4cfc3f8 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1352,7 +1352,8 @@ public: wrapperType_AudioUnitv3, wrapperType_RTAS, wrapperType_AAX, - wrapperType_Standalone + wrapperType_Standalone, + wrapperType_Unity }; /** When loaded by a plugin wrapper, this flag will be set to indicate the type diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index 22d82baf80..3fcac53828 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -207,4 +207,23 @@ void AudioProcessorEditor::setScaleFactor (float newScale) editorResized (true); } +//============================================================================== +#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity + typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); + createUnityPeerFunctionType juce_createUnityPeerFn = nullptr; +#endif + +ComponentPeer* AudioProcessorEditor::createNewPeer (int styleFlags, void* nativeWindow) +{ + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity + if (juce_createUnityPeerFn != nullptr) + { + ignoreUnused (styleFlags, nativeWindow); + return juce_createUnityPeerFn (*this); + } + #endif + + return Component::createNewPeer (styleFlags, nativeWindow); +} + } // namespace juce diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index 76dffc48cd..603dc7f1ac 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -193,6 +193,8 @@ private: JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditorListener) }; + ComponentPeer* createNewPeer (int styleFlags, void*) override; + //============================================================================== void initialise(); void editorResized (bool wasResized); diff --git a/modules/juce_events/native/juce_win32_Messaging.cpp b/modules/juce_events/native/juce_win32_Messaging.cpp index b3760e23a0..7c5d8d3cad 100644 --- a/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/modules/juce_events/native/juce_win32_Messaging.cpp @@ -31,6 +31,10 @@ CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; typedef void (*SettingChangeCallbackFunc) (void); SettingChangeCallbackFunc settingChangeCallback = nullptr; +#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity + bool juce_isRunningInUnity(); +#endif + //============================================================================== namespace WindowsMessageHelpers { @@ -168,6 +172,12 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPend bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) { message->incReferenceCount(); + + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity + if (juce_isRunningInUnity()) + return SendNotifyMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0; + else + #endif return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0; }