/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ /* This monolithic file contains the entire Juce source tree! To build an app which uses Juce, all you need to do is to add this file to your project, and include juce.h in your own cpp files. */ #ifdef __JUCE_JUCEHEADER__ /* When you add the amalgamated cpp file to your project, you mustn't include it in a file where you've already included juce.h - just put it inside a file on its own, possibly with your config flags preceding it, but don't include anything else. */ #error #endif /*** Start of inlined file: juce_TargetPlatform.h ***/ #ifndef __JUCE_TARGETPLATFORM_JUCEHEADER__ #define __JUCE_TARGETPLATFORM_JUCEHEADER__ /* This file figures out which platform is being built, and defines some macros that the rest of the code can use for OS-specific compilation. Macros that will be set here are: - One of JUCE_WINDOWS, JUCE_MAC JUCE_LINUX, JUCE_IOS, JUCE_ANDROID, etc. - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. - Either JUCE_INTEL or JUCE_PPC - Either JUCE_GCC or JUCE_MSVC */ #if (defined (_WIN32) || defined (_WIN64)) #define JUCE_WIN32 1 #define JUCE_WINDOWS 1 #elif defined (JUCE_ANDROID) #undef JUCE_ANDROID #define JUCE_ANDROID 1 #elif defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 #elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) #include // (needed to find out what platform we're using) #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #define JUCE_IPHONE 1 #define JUCE_IOS 1 #else #define JUCE_MAC 1 #endif #else #error "Unknown platform!" #endif #if JUCE_WINDOWS #ifdef _MSC_VER #ifdef _WIN64 #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #endif #ifdef _DEBUG #define JUCE_DEBUG 1 #endif #ifdef __MINGW32__ #define JUCE_MINGW 1 #endif /** If defined, this indicates that the processor is little-endian. */ #define JUCE_LITTLE_ENDIAN 1 #define JUCE_INTEL 1 #endif #if JUCE_MAC || JUCE_IOS #if defined (DEBUG) || defined (_DEBUG) || ! (defined (NDEBUG) || defined (_NDEBUG)) #define JUCE_DEBUG 1 #endif #if ! (defined (DEBUG) || defined (_DEBUG) || defined (NDEBUG) || defined (_NDEBUG)) #warning "Neither NDEBUG or DEBUG has been defined - you should set one of these to make it clear whether this is a release build," #endif #ifdef __LITTLE_ENDIAN__ #define JUCE_LITTLE_ENDIAN 1 #else #define JUCE_BIG_ENDIAN 1 #endif #endif #if JUCE_MAC #if defined (__ppc__) || defined (__ppc64__) #define JUCE_PPC 1 #else #define JUCE_INTEL 1 #endif #ifdef __LP64__ #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4 #error "Building for OSX 10.3 is no longer supported!" #endif #ifndef MAC_OS_X_VERSION_10_5 #error "To build with 10.4 compatibility, use a 10.5 or 10.6 SDK and set the deployment target to 10.4" #endif #endif #if JUCE_LINUX || JUCE_ANDROID #ifdef _DEBUG #define JUCE_DEBUG 1 #endif // Allow override for big-endian Linux platforms #if defined (__LITTLE_ENDIAN__) || ! defined (JUCE_BIG_ENDIAN) #define JUCE_LITTLE_ENDIAN 1 #undef JUCE_BIG_ENDIAN #else #undef JUCE_LITTLE_ENDIAN #define JUCE_BIG_ENDIAN 1 #endif #if defined (__LP64__) || defined (_LP64) #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #if __MMX__ || __SSE__ || __amd64__ #define JUCE_INTEL 1 #endif #endif // Compiler type macros. #ifdef __GNUC__ #define JUCE_GCC 1 #elif defined (_MSC_VER) #define JUCE_MSVC 1 #if _MSC_VER < 1500 #define JUCE_VC8_OR_EARLIER 1 #if _MSC_VER < 1400 #define JUCE_VC7_OR_EARLIER 1 #if _MSC_VER < 1300 #define JUCE_VC6 1 #endif #endif #endif #if ! JUCE_VC7_OR_EARLIER #define JUCE_USE_INTRINSICS 1 #endif #else #error unknown compiler #endif #endif // __JUCE_TARGETPLATFORM_JUCEHEADER__ /*** End of inlined file: juce_TargetPlatform.h ***/ // FORCE_AMALGAMATOR_INCLUDE /*** Start of inlined file: juce_Config.h ***/ #ifndef __JUCE_CONFIG_JUCEHEADER__ #define __JUCE_CONFIG_JUCEHEADER__ /* This file contains macros that enable/disable various JUCE features. */ /** The name of the namespace that all Juce classes and functions will be put inside. If this is not defined, no namespace will be used. */ #ifndef JUCE_NAMESPACE #define JUCE_NAMESPACE juce #endif /** JUCE_FORCE_DEBUG: Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, but if you define this value, you can override this to force it to be true or false. */ #ifndef JUCE_FORCE_DEBUG //#define JUCE_FORCE_DEBUG 0 #endif /** JUCE_LOG_ASSERTIONS: If this flag is enabled, the the jassert and jassertfalse macros will always use Logger::writeToLog() to write a message when an assertion happens. Enabling it will also leave this turned on in release builds. When it's disabled, however, the jassert and jassertfalse macros will not be compiled in a release build. @see jassert, jassertfalse, Logger */ #ifndef JUCE_LOG_ASSERTIONS #define JUCE_LOG_ASSERTIONS 0 #endif /** JUCE_ASIO: Enables ASIO audio devices (MS Windows only). Turning this on means that you'll need to have the Steinberg ASIO SDK installed on your Windows build machine. See the comments in the ASIOAudioIODevice class's header file for more info about this. */ #ifndef JUCE_ASIO #define JUCE_ASIO 0 #endif /** JUCE_WASAPI: Enables WASAPI audio devices (Windows Vista and above). */ #ifndef JUCE_WASAPI #define JUCE_WASAPI 0 #endif /** JUCE_DIRECTSOUND: Enables DirectSound audio (MS Windows only). */ #ifndef JUCE_DIRECTSOUND #define JUCE_DIRECTSOUND 1 #endif /** JUCE_DIRECTSHOW: Enables DirectShow media-streaming architecture (MS Windows only). */ #ifndef JUCE_DIRECTSHOW #define JUCE_DIRECTSHOW 1 #endif /** JUCE_MEDIAFOUNDATION: Enables Media Foundation multimedia platform (Windows Vista and above). */ #ifndef JUCE_MEDIAFOUNDATION #define JUCE_MEDIAFOUNDATION 1 #endif #if ! JUCE_WINDOWS #undef JUCE_DIRECTSHOW #undef JUCE_MEDIAFOUNDATION #endif /** JUCE_ALSA: Enables ALSA audio devices (Linux only). */ #ifndef JUCE_ALSA #define JUCE_ALSA 1 #endif /** JUCE_JACK: Enables JACK audio devices (Linux only). */ #ifndef JUCE_JACK #define JUCE_JACK 0 #endif /** JUCE_QUICKTIME: Enables the QuickTimeMovieComponent class (Mac and Windows). If you're building on Windows, you'll need to have the Apple QuickTime SDK installed, and its header files will need to be on your include path. */ #if ! (defined (JUCE_QUICKTIME) || JUCE_LINUX || JUCE_IOS || JUCE_ANDROID || (JUCE_WINDOWS && ! JUCE_MSVC)) #define JUCE_QUICKTIME 0 #endif #if (JUCE_IOS || JUCE_LINUX) && JUCE_QUICKTIME #undef JUCE_QUICKTIME #endif /** JUCE_OPENGL: Enables the OpenGLComponent class (available on all platforms). If you're not using OpenGL, you might want to turn this off to reduce your binary's size. */ #if ! (defined (JUCE_OPENGL) || JUCE_ANDROID) #define JUCE_OPENGL 1 #endif /** JUCE_DIRECT2D: Enables the Windows 7 Direct2D renderer. If you're building on a platform older than Vista, you won't be able to compile with this feature. */ #ifndef JUCE_DIRECT2D #define JUCE_DIRECT2D 0 #endif /** JUCE_USE_FLAC: Enables the FLAC audio codec classes (available on all platforms). If your app doesn't need to read FLAC files, you might want to disable this to reduce the size of your codebase and build time. */ #ifndef JUCE_USE_FLAC #define JUCE_USE_FLAC 1 #endif /** JUCE_USE_OGGVORBIS: Enables the Ogg-Vorbis audio codec classes (available on all platforms). If your app doesn't need to read Ogg-Vorbis files, you might want to disable this to reduce the size of your codebase and build time. */ #ifndef JUCE_USE_OGGVORBIS #define JUCE_USE_OGGVORBIS 1 #endif /** JUCE_USE_CDBURNER: Enables the audio CD reader code (Mac and Windows only). Unless you're using CD-burning, you should probably turn this flag off to reduce code size. */ #if (! defined (JUCE_USE_CDBURNER)) && ! (JUCE_WINDOWS && ! JUCE_MSVC) #define JUCE_USE_CDBURNER 1 #endif /** JUCE_USE_CDREADER: Enables the audio CD reader code (Mac and Windows only). Unless you're using CD-reading, you should probably turn this flag off to reduce code size. */ #ifndef JUCE_USE_CDREADER #define JUCE_USE_CDREADER 1 #endif /** JUCE_USE_CAMERA: Enables web-cam support using the CameraDevice class (Mac and Windows). */ #if (JUCE_QUICKTIME || JUCE_WINDOWS) && ! defined (JUCE_USE_CAMERA) #define JUCE_USE_CAMERA 0 #endif /** JUCE_ENABLE_REPAINT_DEBUGGING: If this option is turned on, each area of the screen that gets repainted will flash in a random colour, so that you can check exactly how much and how often your components are being drawn. */ #ifndef JUCE_ENABLE_REPAINT_DEBUGGING #define JUCE_ENABLE_REPAINT_DEBUGGING 0 #endif /** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only). Unless you specifically want to disable this, it's best to leave this option turned on. */ #ifndef JUCE_USE_XINERAMA #define JUCE_USE_XINERAMA 1 #endif /** JUCE_USE_XSHM: Enables X shared memory for faster rendering on Linux. This is best left turned on unless you have a good reason to disable it. */ #ifndef JUCE_USE_XSHM #define JUCE_USE_XSHM 1 #endif /** JUCE_USE_XRENDER: Uses XRender to allow semi-transparent windowing on Linux. */ #ifndef JUCE_USE_XRENDER #define JUCE_USE_XRENDER 0 #endif /** JUCE_USE_XCURSOR: Uses XCursor to allow ARGB cursor on Linux. This is best left turned on unless you have a good reason to disable it. */ #ifndef JUCE_USE_XCURSOR #define JUCE_USE_XCURSOR 1 #endif /** JUCE_PLUGINHOST_VST: Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be installed on your machine, and should be left turned off unless you're building a plugin hosting app. @see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU */ #ifndef JUCE_PLUGINHOST_VST #define JUCE_PLUGINHOST_VST 0 #endif /** JUCE_PLUGINHOST_AU: Enables the AudioUnit plugin hosting classes. This is Mac-only, of course, and should only be enabled if you're building a plugin hosting app. @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST */ #ifndef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 0 #endif /** JUCE_ONLY_BUILD_CORE_LIBRARY: Enabling this will avoid including any UI classes in the build. This should be enabled if you're writing a console application. */ #ifndef JUCE_ONLY_BUILD_CORE_LIBRARY #define JUCE_ONLY_BUILD_CORE_LIBRARY 0 #endif /** JUCE_WEB_BROWSER: This lets you disable the WebBrowserComponent class (Mac and Windows). If you're not using any embedded web-pages, turning this off may reduce your code size. */ #ifndef JUCE_WEB_BROWSER #define JUCE_WEB_BROWSER 1 #endif /** JUCE_SUPPORT_CARBON: Enabling this allows the Mac code to use old Carbon library functions. Carbon isn't required for a normal app, but may be needed by specialised classes like plugin-hosts, which support older APIs. */ #if ! (defined (JUCE_SUPPORT_CARBON) || defined (__LP64__)) #define JUCE_SUPPORT_CARBON 1 #endif /* JUCE_INCLUDE_ZLIB_CODE: Can be used to disable Juce's embedded 3rd-party zlib code. You might need to tweak this if you're linking to an external zlib library in your app, but for normal apps, this option should be left alone. */ #ifndef JUCE_INCLUDE_ZLIB_CODE #define JUCE_INCLUDE_ZLIB_CODE 1 #endif #ifndef JUCE_INCLUDE_FLAC_CODE #define JUCE_INCLUDE_FLAC_CODE 1 #endif #ifndef JUCE_INCLUDE_OGGVORBIS_CODE #define JUCE_INCLUDE_OGGVORBIS_CODE 1 #endif #ifndef JUCE_INCLUDE_PNGLIB_CODE #define JUCE_INCLUDE_PNGLIB_CODE 1 #endif #ifndef JUCE_INCLUDE_JPEGLIB_CODE #define JUCE_INCLUDE_JPEGLIB_CODE 1 #endif /** JUCE_CHECK_MEMORY_LEAKS: Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. */ #if JUCE_DEBUG && ! defined (JUCE_CHECK_MEMORY_LEAKS) #define JUCE_CHECK_MEMORY_LEAKS 1 #endif /** JUCE_CATCH_UNHANDLED_EXCEPTIONS: Turn on juce's internal catching of exceptions that are thrown by the message dispatch loop. With it enabled, any unhandled exceptions are passed to the JUCEApplication::unhandledException() callback for logging. */ #ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS #define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 #endif // If only building the core classes, we can explicitly turn off some features to avoid including them: #if JUCE_ONLY_BUILD_CORE_LIBRARY #undef JUCE_QUICKTIME #define JUCE_QUICKTIME 0 #undef JUCE_OPENGL #define JUCE_OPENGL 0 #undef JUCE_USE_CDBURNER #define JUCE_USE_CDBURNER 0 #undef JUCE_USE_CDREADER #define JUCE_USE_CDREADER 0 #undef JUCE_WEB_BROWSER #define JUCE_WEB_BROWSER 0 #undef JUCE_PLUGINHOST_AU #define JUCE_PLUGINHOST_AU 0 #undef JUCE_PLUGINHOST_VST #define JUCE_PLUGINHOST_VST 0 #endif #endif /*** End of inlined file: juce_Config.h ***/ // FORCE_AMALGAMATOR_INCLUDE #ifndef JUCE_BUILD_CORE #define JUCE_BUILD_CORE 1 #endif #ifndef JUCE_BUILD_MISC #define JUCE_BUILD_MISC 1 #endif #ifndef JUCE_BUILD_GUI #define JUCE_BUILD_GUI 1 #endif #ifndef JUCE_BUILD_NATIVE #define JUCE_BUILD_NATIVE 1 #endif #if JUCE_ONLY_BUILD_CORE_LIBRARY #undef JUCE_BUILD_MISC #undef JUCE_BUILD_GUI #endif //============================================================================== #if JUCE_BUILD_NATIVE || JUCE_BUILD_CORE || (JUCE_BUILD_MISC && (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU)) #if JUCE_WINDOWS /*** Start of inlined file: juce_win32_NativeIncludes.h ***/ #ifndef __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ #ifndef STRICT #define STRICT 1 #endif #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #if JUCE_MSVC #ifndef _CPPRTTI #error "You're compiling without RTTI enabled! This is needed for a lot of JUCE classes, please update your compiler settings!" #endif #ifndef _CPPUNWIND #error "You're compiling without exceptions enabled! This is needed for a lot of JUCE classes, please update your compiler settings!" #endif #pragma warning (push) #pragma warning (disable : 4100 4201 4514 4312 4995) #endif #define _WIN32_WINNT 0x0500 #define _UNICODE 1 #define UNICODE 1 #ifndef _WIN32_IE #define _WIN32_IE 0x0400 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ! JUCE_MINGW #include #include #endif #if JUCE_OPENGL #include #endif #undef PACKED #if JUCE_ASIO && JUCE_BUILD_NATIVE /* This is very frustrating - we only need to use a handful of definitions from a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy about 30 lines of code into this cpp file to create a fully stand-alone ASIO implementation... ..unfortunately that would break Steinberg's license agreement for use of their SDK, so I'm not allowed to do this. This means that anyone who wants to use JUCE's ASIO abilities will have to: 1) Agree to Steinberg's licensing terms and download the ASIO SDK (see www.steinberg.net/Steinberg/Developers.asp). 2) Enable this code with a global definition #define JUCE_ASIO 1. 3) Make sure that your header search path contains the iasiodrv.h file that comes with the SDK. (Only about a handful of the SDK header files are actually needed - so to simplify things, you could just copy these into your JUCE directory). */ #include #endif #if JUCE_USE_CDBURNER && JUCE_BUILD_NATIVE /* You'll need the Platform SDK for these headers - if you don't have it and don't need to use CD-burning, then you might just want to disable the JUCE_USE_CDBURNER flag in juce_Config.h to avoid these includes. */ #include #include #endif #if JUCE_USE_CAMERA && JUCE_BUILD_NATIVE /* If you're using the camera classes, you'll need access to a few DirectShow headers. These files are provided in the normal Windows SDK, but some Microsoft plonker didn't realise that qedit.h doesn't actually compile without the rest of the DirectShow SDK.. Microsoft's suggested fix for this is to hack their qedit.h file! See: http://social.msdn.microsoft.com/Forums/en-US/windowssdk/thread/ed097d2c-3d68-4f48-8448-277eaaf68252 .. which is a bit of a bodge, but a lot less hassle than installing the full DShow SDK. An alternative workaround is to create a dummy dxtrans.h file and put it in your include path. The dummy file just needs to contain the following content: #define __IDxtCompositor_INTERFACE_DEFINED__ #define __IDxtAlphaSetter_INTERFACE_DEFINED__ #define __IDxtJpeg_INTERFACE_DEFINED__ #define __IDxtKey_INTERFACE_DEFINED__ ..and that should be enough to convince qedit.h that you have the SDK! */ #include #include #include #endif #if JUCE_WASAPI && JUCE_BUILD_NATIVE #include #include #include #include #include #include #endif #if JUCE_QUICKTIME /* If you've got an include error here, you probably need to install the QuickTime SDK and add its header directory to your include path. Alternatively, if you don't need any QuickTime services, just turn off the JUCE_QUICKTIME flag in juce_Config.h */ #include #include #include #include #include /* If you've got QuickTime 7 installed, then these COM objects should be found in the "\Program Files\Quicktime" directory. You'll need to add this directory to your include search path to make these import statements work. */ #import #import #endif #if JUCE_DIRECTSHOW && JUCE_BUILD_NATIVE #include #endif #if JUCE_MEDIAFOUNDATION && JUCE_BUILD_NATIVE #include #endif #if JUCE_MSVC #pragma warning (pop) #endif #if JUCE_DIRECT2D && JUCE_BUILD_NATIVE #include #include #endif #ifndef WM_APPCOMMAND #define WM_APPCOMMAND 0x0319 #endif /** A simple COM smart pointer. Avoids having to include ATL just to get one of these. */ template class ComSmartPtr { public: ComSmartPtr() throw() : p (0) {} ComSmartPtr (ComClass* const p_) : p (p_) { if (p_ != 0) p_->AddRef(); } ComSmartPtr (const ComSmartPtr& p_) : p (p_.p) { if (p != 0) p->AddRef(); } ~ComSmartPtr() { release(); } operator ComClass*() const throw() { return p; } ComClass& operator*() const throw() { return *p; } ComClass* operator->() const throw() { return p; } ComSmartPtr& operator= (ComClass* const newP) { if (newP != 0) newP->AddRef(); release(); p = newP; return *this; } ComSmartPtr& operator= (const ComSmartPtr& newP) { return operator= (newP.p); } // Releases and nullifies this pointer and returns its address ComClass** resetAndGetPointerAddress() { release(); p = 0; return &p; } HRESULT CoCreateInstance (REFCLSID classUUID, DWORD dwClsContext = CLSCTX_INPROC_SERVER) { #ifndef __MINGW32__ return ::CoCreateInstance (classUUID, 0, dwClsContext, __uuidof (ComClass), (void**) resetAndGetPointerAddress()); #else return E_NOTIMPL; #endif } template HRESULT QueryInterface (REFCLSID classUUID, ComSmartPtr& destObject) const { if (p == 0) return E_POINTER; return p->QueryInterface (classUUID, (void**) destObject.resetAndGetPointerAddress()); } private: ComClass* p; void release() { if (p != 0) p->Release(); } ComClass** operator&() throw(); // private to avoid it being used accidentally }; /** Handy base class for writing COM objects, providing ref-counting and a basic QueryInterface method. */ template class ComBaseClassHelper : public ComClass { public: ComBaseClassHelper() : refCount (1) {} virtual ~ComBaseClassHelper() {} HRESULT __stdcall QueryInterface (REFIID refId, void** result) { #ifndef __MINGW32__ if (refId == __uuidof (ComClass)) { AddRef(); *result = dynamic_cast (this); return S_OK; } #endif if (refId == IID_IUnknown) { AddRef(); *result = dynamic_cast (this); return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { const int r = --refCount; if (r == 0) delete this; return r; } protected: int refCount; }; #endif // __JUCE_WIN32_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_win32_NativeIncludes.h ***/ #elif JUCE_LINUX /*** Start of inlined file: juce_linux_NativeIncludes.h ***/ #ifndef __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ /* This file wraps together all the linux-specific headers, so that we can include them all just once, and compile all our platform-specific stuff in one big lump, keeping it out of the way of the rest of the codebase. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Got a build error here? You'll need to install the freetype library... The name of the package to install is "libfreetype6-dev". */ #include #include FT_FREETYPE_H #include #include #include #include #include #include #include #if JUCE_USE_XINERAMA /* If you're trying to use Xinerama, you'll need to install the "libxinerama-dev" package.. */ #include #endif #if JUCE_USE_XSHM #include #include #include #endif #if JUCE_USE_XRENDER // If you're missing these headers, try installing the libxrender-dev and libxcomposite-dev #include #include #endif #if JUCE_USE_XCURSOR // If you're missing this header, try installing the libxcursor-dev package #include #endif #if JUCE_OPENGL /* Got an include error here? If you want to install OpenGL support, the packages to get are "mesa-common-dev" and "freeglut3-dev". Alternatively, you can turn off the JUCE_OPENGL flag in juce_Config.h if you want to disable it. */ #include #endif #undef KeyPress #if JUCE_ALSA /* Got an include error here? If so, you've either not got ALSA installed, or you've not got your paths set up correctly to find its header files. The package you need to install to get ASLA support is "libasound2-dev". If you don't have the ALSA library and don't want to build Juce with audio support, just disable the JUCE_ALSA flag in juce_Config.h */ #include #endif #if JUCE_JACK /* Got an include error here? If so, you've either not got jack-audio-connection-kit installed, or you've not got your paths set up correctly to find its header files. The package you need to install to get JACK support is "libjack-dev". If you don't have the jack-audio-connection-kit library and don't want to build Juce with low latency audio support, just disable the JUCE_JACK flag in juce_Config.h */ #include //#include #endif #undef SIZEOF #endif // __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_linux_NativeIncludes.h ***/ #elif JUCE_MAC || JUCE_IOS /*** Start of inlined file: juce_mac_NativeIncludes.h ***/ #ifndef __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ #define USE_COREGRAPHICS_RENDERING 1 #if JUCE_IOS #import #import #import #import #import #import #import #import #include #if JUCE_OPENGL #include #include #endif #else #import #import #if JUCE_BUILD_NATIVE #import #import #import #import #import #import #import #import #import #import #endif #if (JUCE_BUILD_MISC && (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU)) \ || ! (defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) #include #endif #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if MACOS_10_4_OR_EARLIER #include #endif #if ! CGFLOAT_DEFINED #define CGFloat float #endif #endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_mac_NativeIncludes.h ***/ #elif JUCE_ANDROID /*** Start of inlined file: juce_android_NativeIncludes.h ***/ #ifndef __JUCE_ANDROID_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_ANDROID_NATIVEINCLUDES_JUCEHEADER__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if JUCE_OPENGL #include #endif #endif // __JUCE_ANDROID_NATIVEINCLUDES_JUCEHEADER__ /*** End of inlined file: juce_android_NativeIncludes.h ***/ #else #error "Unknown platform!" #endif #endif //============================================================================== #define DONT_SET_USING_JUCE_NAMESPACE 1 #undef max #undef min #define NO_DUMMY_DECL #define JUCE_AMALGAMATED_TEMPLATE 1 #if JUCE_BUILD_NATIVE #include "juce_amalgamated.h" // FORCE_AMALGAMATOR_INCLUDE #endif #if (defined(_MSC_VER) && (_MSC_VER <= 1200)) #pragma warning (disable: 4309 4305) #endif #if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON && JUCE_BUILD_NATIVE && ! JUCE_ONLY_BUILD_CORE_LIBRARY BEGIN_JUCE_NAMESPACE /*** Start of inlined file: juce_mac_CarbonViewWrapperComponent.h ***/ #ifndef __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ #define __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ /** Creates a floating carbon window that can be used to hold a carbon UI. This is a handy class that's designed to be inlined where needed, e.g. in the audio plugin hosting code. */ class CarbonViewWrapperComponent : public Component, public ComponentMovementWatcher, public Timer { public: CarbonViewWrapperComponent() : ComponentMovementWatcher (this), wrapperWindow (0), carbonWindow (0), embeddedView (0), recursiveResize (false) { } virtual ~CarbonViewWrapperComponent() { jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor! } virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0; virtual void removeView (HIViewRef embeddedView) = 0; virtual void mouseDown (int, int) {} virtual void paint() {} virtual bool getEmbeddedViewSize (int& w, int& h) { if (embeddedView == 0) return false; HIRect bounds; HIViewGetBounds (embeddedView, &bounds); w = jmax (1, roundToInt (bounds.size.width)); h = jmax (1, roundToInt (bounds.size.height)); return true; } void createWindow() { if (wrapperWindow == 0) { Rect r; r.left = getScreenX(); r.top = getScreenY(); r.right = r.left + getWidth(); r.bottom = r.top + getHeight(); CreateNewWindow (kDocumentWindowClass, (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute), &r, &wrapperWindow); jassert (wrapperWindow != 0); if (wrapperWindow == 0) return; carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow]; NSWindow* ownerWindow = [((NSView*) getWindowHandle()) window]; [ownerWindow addChildWindow: carbonWindow ordered: NSWindowAbove]; embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow)); EventTypeSpec windowEventTypes[] = { { kEventClassWindow, kEventWindowGetClickActivation }, { kEventClassWindow, kEventWindowHandleDeactivate }, { kEventClassWindow, kEventWindowBoundsChanging }, { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseUp}, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowHidden } }; EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback); InstallWindowEventHandler (wrapperWindow, upp, sizeof (windowEventTypes) / sizeof (EventTypeSpec), windowEventTypes, this, &eventHandlerRef); setOurSizeToEmbeddedViewSize(); setEmbeddedWindowToOurSize(); creationTime = Time::getCurrentTime(); } } void deleteWindow() { removeView (embeddedView); embeddedView = 0; if (wrapperWindow != 0) { RemoveEventHandler (eventHandlerRef); DisposeWindow (wrapperWindow); wrapperWindow = 0; } } void setOurSizeToEmbeddedViewSize() { int w, h; if (getEmbeddedViewSize (w, h)) { if (w != getWidth() || h != getHeight()) { startTimer (50); setSize (w, h); if (getParentComponent() != nullptr) getParentComponent()->setSize (w, h); } else { startTimer (jlimit (50, 500, getTimerInterval() + 20)); } } else { stopTimer(); } } void setEmbeddedWindowToOurSize() { if (! recursiveResize) { recursiveResize = true; if (embeddedView != 0) { HIRect r; r.origin.x = 0; r.origin.y = 0; r.size.width = (float) getWidth(); r.size.height = (float) getHeight(); HIViewSetFrame (embeddedView, &r); } if (wrapperWindow != 0) { Rect wr; wr.left = getScreenX(); wr.top = getScreenY(); wr.right = wr.left + getWidth(); wr.bottom = wr.top + getHeight(); SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr); ShowWindow (wrapperWindow); } recursiveResize = false; } } void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { setEmbeddedWindowToOurSize(); } void componentPeerChanged() { deleteWindow(); createWindow(); } void componentVisibilityChanged() { if (isShowing()) createWindow(); else deleteWindow(); setEmbeddedWindowToOurSize(); } static void recursiveHIViewRepaint (HIViewRef view) { HIViewSetNeedsDisplay (view, true); HIViewRef child = HIViewGetFirstSubview (view); while (child != 0) { recursiveHIViewRepaint (child); child = HIViewGetNextView (child); } } void timerCallback() { setOurSizeToEmbeddedViewSize(); // To avoid strange overpainting problems when the UI is first opened, we'll // repaint it a few times during the first second that it's on-screen.. if ((Time::getCurrentTime() - creationTime).inMilliseconds() < 1000) recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow)); } OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event) { switch (GetEventKind (event)) { case kEventWindowHandleDeactivate: ActivateWindow (wrapperWindow, TRUE); return noErr; case kEventWindowGetClickActivation: { getTopLevelComponent()->toFront (false); [carbonWindow makeKeyAndOrderFront: nil]; ClickActivationResult howToHandleClick = kActivateAndHandleClick; SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult, sizeof (ClickActivationResult), &howToHandleClick); HIViewSetNeedsDisplay (embeddedView, true); return noErr; } } return eventNotHandledErr; } static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData) { return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event); } protected: WindowRef wrapperWindow; NSWindow* carbonWindow; HIViewRef embeddedView; bool recursiveResize; Time creationTime; EventHandlerRef eventHandlerRef; }; #endif // __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__ /*** End of inlined file: juce_mac_CarbonViewWrapperComponent.h ***/ END_JUCE_NAMESPACE #endif //============================================================================== #if JUCE_BUILD_CORE /*** Start of inlined file: juce_FileLogger.cpp ***/ BEGIN_JUCE_NAMESPACE FileLogger::FileLogger (const File& logFile_, const String& welcomeMessage, const int maxInitialFileSizeBytes) : logFile (logFile_) { if (maxInitialFileSizeBytes >= 0) trimFileSize (maxInitialFileSizeBytes); if (! logFile_.exists()) { // do this so that the parent directories get created.. logFile_.create(); } String welcome; welcome << newLine << "**********************************************************" << newLine << welcomeMessage << newLine << "Log started: " << Time::getCurrentTime().toString (true, true) << newLine; FileLogger::logMessage (welcome); } FileLogger::~FileLogger() { } void FileLogger::logMessage (const String& message) { DBG (message); const ScopedLock sl (logLock); FileOutputStream out (logFile, 256); out << message << newLine; } void FileLogger::trimFileSize (int maxFileSizeBytes) const { if (maxFileSizeBytes <= 0) { logFile.deleteFile(); } else { const int64 fileSize = logFile.getSize(); if (fileSize > maxFileSizeBytes) { ScopedPointer in (logFile.createInputStream()); jassert (in != nullptr); if (in != nullptr) { in->setPosition (fileSize - maxFileSizeBytes); String content; { MemoryBlock contentToSave; contentToSave.setSize (maxFileSizeBytes + 4); contentToSave.fillWith (0); in->read (contentToSave.getData(), maxFileSizeBytes); in = nullptr; content = contentToSave.toString(); } int newStart = 0; while (newStart < fileSize && content[newStart] != '\n' && content[newStart] != '\r') ++newStart; logFile.deleteFile(); logFile.appendText (content.substring (newStart), false, false); } } } } FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, const String& logFileName, const String& welcomeMessage, const int maxInitialFileSizeBytes) { #if JUCE_MAC File logFile ("~/Library/Logs"); logFile = logFile.getChildFile (logFileSubDirectoryName) .getChildFile (logFileName); #else File logFile (File::getSpecialLocation (File::userApplicationDataDirectory)); if (logFile.isDirectory()) { logFile = logFile.getChildFile (logFileSubDirectoryName) .getChildFile (logFileName); } #endif return new FileLogger (logFile, welcomeMessage, maxInitialFileSizeBytes); } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileLogger.cpp ***/ /*** Start of inlined file: juce_Logger.cpp ***/ BEGIN_JUCE_NAMESPACE Logger::Logger() { } Logger::~Logger() { } Logger* Logger::currentLogger = nullptr; void Logger::setCurrentLogger (Logger* const newLogger, const bool deleteOldLogger) { Logger* const oldLogger = currentLogger; currentLogger = newLogger; if (deleteOldLogger) delete oldLogger; } void Logger::writeToLog (const String& message) { if (currentLogger != nullptr) currentLogger->logMessage (message); else outputDebugString (message); } #if JUCE_LOG_ASSERTIONS void JUCE_API juce_LogAssertion (const char* filename, const int lineNum) noexcept { String m ("JUCE Assertion failure in "); m << filename << ", line " << lineNum; Logger::writeToLog (m); } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Logger.cpp ***/ /*** Start of inlined file: juce_Random.cpp ***/ BEGIN_JUCE_NAMESPACE Random::Random (const int64 seedValue) noexcept : seed (seedValue) { } Random::Random() : seed (1) { setSeedRandomly(); } Random::~Random() noexcept { } void Random::setSeed (const int64 newSeed) noexcept { seed = newSeed; } void Random::combineSeed (const int64 seedValue) noexcept { seed ^= nextInt64() ^ seedValue; } void Random::setSeedRandomly() { combineSeed ((int64) (pointer_sized_int) this); combineSeed (Time::getMillisecondCounter()); combineSeed (Time::getHighResolutionTicks()); combineSeed (Time::getHighResolutionTicksPerSecond()); combineSeed (Time::currentTimeMillis()); } Random& Random::getSystemRandom() noexcept { static Random sysRand; return sysRand; } int Random::nextInt() noexcept { seed = (seed * literal64bit (0x5deece66d) + 11) & literal64bit (0xffffffffffff); return (int) (seed >> 16); } int Random::nextInt (const int maxValue) noexcept { jassert (maxValue > 0); return (nextInt() & 0x7fffffff) % maxValue; } int64 Random::nextInt64() noexcept { return (((int64) nextInt()) << 32) | (int64) (uint64) (uint32) nextInt(); } bool Random::nextBool() noexcept { return (nextInt() & 0x80000000) != 0; } float Random::nextFloat() noexcept { return static_cast (nextInt()) / (float) 0xffffffff; } double Random::nextDouble() noexcept { return static_cast (nextInt()) / (double) 0xffffffff; } BigInteger Random::nextLargeNumber (const BigInteger& maximumValue) { BigInteger n; do { fillBitsRandomly (n, 0, maximumValue.getHighestBit() + 1); } while (n >= maximumValue); return n; } void Random::fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits) { arrayToChange.setBit (startBit + numBits - 1, true); // to force the array to pre-allocate space while ((startBit & 31) != 0 && numBits > 0) { arrayToChange.setBit (startBit++, nextBool()); --numBits; } while (numBits >= 32) { arrayToChange.setBitRangeAsInt (startBit, 32, (unsigned int) nextInt()); startBit += 32; numBits -= 32; } while (--numBits >= 0) arrayToChange.setBit (startBit + numBits, nextBool()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Random.cpp ***/ /*** Start of inlined file: juce_RelativeTime.cpp ***/ BEGIN_JUCE_NAMESPACE RelativeTime::RelativeTime (const double seconds_) noexcept : seconds (seconds_) { } RelativeTime::RelativeTime (const RelativeTime& other) noexcept : seconds (other.seconds) { } RelativeTime::~RelativeTime() noexcept { } const RelativeTime RelativeTime::milliseconds (const int milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::milliseconds (const int64 milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::minutes (const double numberOfMinutes) noexcept { return RelativeTime (numberOfMinutes * 60.0); } const RelativeTime RelativeTime::hours (const double numberOfHours) noexcept { return RelativeTime (numberOfHours * (60.0 * 60.0)); } const RelativeTime RelativeTime::days (const double numberOfDays) noexcept { return RelativeTime (numberOfDays * (60.0 * 60.0 * 24.0)); } const RelativeTime RelativeTime::weeks (const double numberOfWeeks) noexcept { return RelativeTime (numberOfWeeks * (60.0 * 60.0 * 24.0 * 7.0)); } int64 RelativeTime::inMilliseconds() const noexcept { return (int64) (seconds * 1000.0); } double RelativeTime::inMinutes() const noexcept { return seconds / 60.0; } double RelativeTime::inHours() const noexcept { return seconds / (60.0 * 60.0); } double RelativeTime::inDays() const noexcept { return seconds / (60.0 * 60.0 * 24.0); } double RelativeTime::inWeeks() const noexcept { return seconds / (60.0 * 60.0 * 24.0 * 7.0); } String RelativeTime::getDescription (const String& returnValueForZeroTime) const { if (seconds < 0.001 && seconds > -0.001) return returnValueForZeroTime; String result; result.preallocateBytes (32); if (seconds < 0) result << '-'; int fieldsShown = 0; int n = std::abs ((int) inWeeks()); if (n > 0) { result << n << (n == 1 ? TRANS(" week ") : TRANS(" weeks ")); ++fieldsShown; } n = std::abs ((int) inDays()) % 7; if (n > 0) { result << n << (n == 1 ? TRANS(" day ") : TRANS(" days ")); ++fieldsShown; } if (fieldsShown < 2) { n = std::abs ((int) inHours()) % 24; if (n > 0) { result << n << (n == 1 ? TRANS(" hr ") : TRANS(" hrs ")); ++fieldsShown; } if (fieldsShown < 2) { n = std::abs ((int) inMinutes()) % 60; if (n > 0) { result << n << (n == 1 ? TRANS(" min ") : TRANS(" mins ")); ++fieldsShown; } if (fieldsShown < 2) { n = std::abs ((int) inSeconds()) % 60; if (n > 0) { result << n << (n == 1 ? TRANS(" sec ") : TRANS(" secs ")); ++fieldsShown; } if (fieldsShown == 0) { n = std::abs ((int) inMilliseconds()) % 1000; if (n > 0) result << n << TRANS(" ms"); } } } } return result.trimEnd(); } RelativeTime& RelativeTime::operator= (const RelativeTime& other) noexcept { seconds = other.seconds; return *this; } const RelativeTime& RelativeTime::operator+= (const RelativeTime& timeToAdd) noexcept { seconds += timeToAdd.seconds; return *this; } const RelativeTime& RelativeTime::operator-= (const RelativeTime& timeToSubtract) noexcept { seconds -= timeToSubtract.seconds; return *this; } const RelativeTime& RelativeTime::operator+= (const double secondsToAdd) noexcept { seconds += secondsToAdd; return *this; } const RelativeTime& RelativeTime::operator-= (const double secondsToSubtract) noexcept { seconds -= secondsToSubtract; return *this; } bool operator== (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() == t2.inSeconds(); } bool operator!= (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() != t2.inSeconds(); } bool operator> (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() > t2.inSeconds(); } bool operator< (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() < t2.inSeconds(); } bool operator>= (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() >= t2.inSeconds(); } bool operator<= (const RelativeTime& t1, const RelativeTime& t2) noexcept { return t1.inSeconds() <= t2.inSeconds(); } RelativeTime operator+ (const RelativeTime& t1, const RelativeTime& t2) noexcept { RelativeTime t (t1); return t += t2; } RelativeTime operator- (const RelativeTime& t1, const RelativeTime& t2) noexcept { RelativeTime t (t1); return t -= t2; } END_JUCE_NAMESPACE /*** End of inlined file: juce_RelativeTime.cpp ***/ /*** Start of inlined file: juce_SystemStats.cpp ***/ BEGIN_JUCE_NAMESPACE const SystemStats::CPUFlags& SystemStats::getCPUFlags() { static CPUFlags cpuFlags; return cpuFlags; } const String SystemStats::getJUCEVersion() { // Some basic tests, to keep an eye on things and make sure these types work ok // on all platforms. Let me know if any of these assertions fail on your system! static_jassert (sizeof (pointer_sized_int) == sizeof (void*)); static_jassert (sizeof (int8) == 1); static_jassert (sizeof (uint8) == 1); static_jassert (sizeof (int16) == 2); static_jassert (sizeof (uint16) == 2); static_jassert (sizeof (int32) == 4); static_jassert (sizeof (uint32) == 4); static_jassert (sizeof (int64) == 8); static_jassert (sizeof (uint64) == 8); // (these confusing macros convert numbers into a single string literal) #define JUCE_STRINGIFYVERSION2(a) #a #define JUCE_STRINGIFYVERSION(a) JUCE_STRINGIFYVERSION2(a) return "JUCE v" JUCE_STRINGIFYVERSION(JUCE_MAJOR_VERSION) "." JUCE_STRINGIFYVERSION(JUCE_MINOR_VERSION) "." JUCE_STRINGIFYVERSION(JUCE_BUILDNUMBER); #undef JUCE_STRINGIFYVERSION #undef JUCE_STRINGIFYVERSION2 } #if JUCE_DEBUG && ! JUCE_ANDROID struct JuceVersionPrinter { JuceVersionPrinter() { DBG (SystemStats::getJUCEVersion()); } }; static JuceVersionPrinter juceVersionPrinter; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_SystemStats.cpp ***/ /*** Start of inlined file: juce_Result.cpp ***/ BEGIN_JUCE_NAMESPACE Result::Result (const String& message) noexcept : errorMessage (message) { } Result::Result (const Result& other) : errorMessage (other.errorMessage) { } Result& Result::operator= (const Result& other) { errorMessage = other.errorMessage; return *this; } bool Result::operator== (const Result& other) const noexcept { return errorMessage == other.errorMessage; } bool Result::operator!= (const Result& other) const noexcept { return errorMessage != other.errorMessage; } Result Result::ok() noexcept { return Result (String::empty); } Result Result::fail (const String& errorMessage) noexcept { return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); } const String& Result::getErrorMessage() const noexcept { return errorMessage; } bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } Result::operator bool() const noexcept { return errorMessage.isEmpty(); } bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Result.cpp ***/ /*** Start of inlined file: juce_Time.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514) #endif #ifndef JUCE_WINDOWS #include #else #include #endif #include #if JUCE_MSVC #pragma warning (pop) #ifdef _INC_TIME_INL #define USE_NEW_SECURE_TIME_FNS #endif #endif BEGIN_JUCE_NAMESPACE namespace TimeHelpers { struct tm millisToLocal (const int64 millis) noexcept { struct tm result; const int64 seconds = millis / 1000; if (seconds < literal64bit (86400) || seconds >= literal64bit (2145916800)) { // use extended maths for dates beyond 1970 to 2037.. const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); const int64 jdm = seconds + timeZoneAdjustment + literal64bit (210866803200); const int days = (int) (jdm / literal64bit (86400)); const int a = 32044 + days; const int b = (4 * a + 3) / 146097; const int c = a - (b * 146097) / 4; const int d = (4 * c + 3) / 1461; const int e = c - (d * 1461) / 4; const int m = (5 * e + 2) / 153; result.tm_mday = e - (153 * m + 2) / 5 + 1; result.tm_mon = m + 2 - 12 * (m / 10); result.tm_year = b * 100 + d - 6700 + (m / 10); result.tm_wday = (days + 1) % 7; result.tm_yday = -1; int t = (int) (jdm % literal64bit (86400)); result.tm_hour = t / 3600; t %= 3600; result.tm_min = t / 60; result.tm_sec = t % 60; result.tm_isdst = -1; } else { time_t now = static_cast (seconds); #if JUCE_WINDOWS #ifdef USE_NEW_SECURE_TIME_FNS if (now >= 0 && now <= 0x793406fff) localtime_s (&result, &now); else zerostruct (result); #else result = *localtime (&now); #endif #else localtime_r (&now, &result); // more thread-safe #endif } return result; } int extendedModulo (const int64 value, const int modulo) noexcept { return (int) (value >= 0 ? (value % modulo) : (value - ((value / modulo) + 1) * modulo)); } int doFTime (CharPointer_UTF32 dest, const int maxChars, const String& format, const struct tm* const tm) noexcept { #if JUCE_ANDROID HeapBlock tempDest; tempDest.calloc (maxChars + 2); const int result = (int) strftime (tempDest, maxChars, format.toUTF8(), tm); if (result > 0) dest.writeAll (CharPointer_UTF8 (tempDest.getData())); return result; #elif JUCE_WINDOWS HeapBlock tempDest; tempDest.calloc (maxChars + 2); const int result = (int) wcsftime (tempDest, maxChars, format.toWideCharPointer(), tm); if (result > 0) dest.writeAll (CharPointer_UTF16 (tempDest.getData())); return result; #else return (int) wcsftime (dest.getAddress(), maxChars, format.toUTF32(), tm); #endif } static uint32 lastMSCounterValue = 0; } Time::Time() noexcept : millisSinceEpoch (0) { } Time::Time (const Time& other) noexcept : millisSinceEpoch (other.millisSinceEpoch) { } Time::Time (const int64 ms) noexcept : millisSinceEpoch (ms) { } Time::Time (const int year, const int month, const int day, const int hours, const int minutes, const int seconds, const int milliseconds, const bool useLocalTime) noexcept { jassert (year > 100); // year must be a 4-digit version if (year < 1971 || year >= 2038 || ! useLocalTime) { // use extended maths for dates beyond 1970 to 2037.. const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) : 0; const int a = (13 - month) / 12; const int y = year + 4800 - a; const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 + (y * 365) + (y / 4) - (y / 100) + (y / 400) - 32045; const int64 s = ((int64) jd) * literal64bit (86400) - literal64bit (210866803200); millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) + milliseconds; } else { struct tm t; t.tm_year = year - 1900; t.tm_mon = month; t.tm_mday = day; t.tm_hour = hours; t.tm_min = minutes; t.tm_sec = seconds; t.tm_isdst = -1; millisSinceEpoch = 1000 * (int64) mktime (&t); if (millisSinceEpoch < 0) millisSinceEpoch = 0; else millisSinceEpoch += milliseconds; } } Time::~Time() noexcept { } Time& Time::operator= (const Time& other) noexcept { millisSinceEpoch = other.millisSinceEpoch; return *this; } int64 Time::currentTimeMillis() noexcept { static uint32 lastCounterResult = 0xffffffff; static int64 correction = 0; const uint32 now = getMillisecondCounter(); // check the counter hasn't wrapped (also triggered the first time this function is called) if (now < lastCounterResult) { // double-check it's actually wrapped, in case multi-cpu machines have timers that drift a bit. if (lastCounterResult == 0xffffffff || now < lastCounterResult - 10) { // get the time once using normal library calls, and store the difference needed to // turn the millisecond counter into a real time. #if JUCE_WINDOWS struct _timeb t; #ifdef USE_NEW_SECURE_TIME_FNS _ftime_s (&t); #else _ftime (&t); #endif correction = (((int64) t.time) * 1000 + t.millitm) - now; #else struct timeval tv; struct timezone tz; gettimeofday (&tv, &tz); correction = (((int64) tv.tv_sec) * 1000 + tv.tv_usec / 1000) - now; #endif } } lastCounterResult = now; return correction + now; } uint32 juce_millisecondsSinceStartup() noexcept; uint32 Time::getMillisecondCounter() noexcept { const uint32 now = juce_millisecondsSinceStartup(); if (now < TimeHelpers::lastMSCounterValue) { // in multi-threaded apps this might be called concurrently, so // make sure that our last counter value only increases and doesn't // go backwards.. if (now < TimeHelpers::lastMSCounterValue - 1000) TimeHelpers::lastMSCounterValue = now; } else { TimeHelpers::lastMSCounterValue = now; } return now; } uint32 Time::getApproximateMillisecondCounter() noexcept { if (TimeHelpers::lastMSCounterValue == 0) getMillisecondCounter(); return TimeHelpers::lastMSCounterValue; } void Time::waitForMillisecondCounter (const uint32 targetTime) noexcept { for (;;) { const uint32 now = getMillisecondCounter(); if (now >= targetTime) break; const int toWait = targetTime - now; if (toWait > 2) { Thread::sleep (jmin (20, toWait >> 1)); } else { // xxx should consider using mutex_pause on the mac as it apparently // makes it seem less like a spinlock and avoids lowering the thread pri. for (int i = 10; --i >= 0;) Thread::yield(); } } } double Time::highResolutionTicksToSeconds (const int64 ticks) noexcept { return ticks / (double) getHighResolutionTicksPerSecond(); } int64 Time::secondsToHighResolutionTicks (const double seconds) noexcept { return (int64) (seconds * (double) getHighResolutionTicksPerSecond()); } Time JUCE_CALLTYPE Time::getCurrentTime() noexcept { return Time (currentTimeMillis()); } String Time::toString (const bool includeDate, const bool includeTime, const bool includeSeconds, const bool use24HourClock) const noexcept { String result; if (includeDate) { result << getDayOfMonth() << ' ' << getMonthName (true) << ' ' << getYear(); if (includeTime) result << ' '; } if (includeTime) { const int mins = getMinutes(); result << (use24HourClock ? getHours() : getHoursInAmPmFormat()) << (mins < 10 ? ":0" : ":") << mins; if (includeSeconds) { const int secs = getSeconds(); result << (secs < 10 ? ":0" : ":") << secs; } if (! use24HourClock) result << (isAfternoon() ? "pm" : "am"); } return result.trimEnd(); } String Time::formatted (const String& format) const { int bufferSize = 128; HeapBlock buffer (128); struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); while (TimeHelpers::doFTime (CharPointer_UTF32 (buffer.getData()), bufferSize, format, &t) <= 0) { bufferSize += 128; buffer.malloc (bufferSize); } return CharPointer_UTF32 (buffer.getData()); } int Time::getYear() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_year + 1900; } int Time::getMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mon; } int Time::getDayOfMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mday; } int Time::getDayOfWeek() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_wday; } int Time::getHours() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_hour; } int Time::getMinutes() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_min; } int Time::getSeconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch / 1000, 60); } int Time::getMilliseconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch, 1000); } int Time::getHoursInAmPmFormat() const noexcept { const int hours = getHours(); if (hours == 0) return 12; else if (hours <= 12) return hours; else return hours - 12; } bool Time::isAfternoon() const noexcept { return getHours() >= 12; } bool Time::isDaylightSavingTime() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_isdst != 0; } String Time::getTimeZone() const noexcept { String zone[2]; #if JUCE_WINDOWS _tzset(); #ifdef USE_NEW_SECURE_TIME_FNS for (int i = 0; i < 2; ++i) { char name[128] = { 0 }; size_t length; _get_tzname (&length, name, 127, i); zone[i] = name; } #else const char** const zonePtr = (const char**) _tzname; zone[0] = zonePtr[0]; zone[1] = zonePtr[1]; #endif #else tzset(); const char** const zonePtr = (const char**) tzname; zone[0] = zonePtr[0]; zone[1] = zonePtr[1]; #endif if (isDaylightSavingTime()) { zone[0] = zone[1]; if (zone[0].length() > 3 && zone[0].containsIgnoreCase ("daylight") && zone[0].contains ("GMT")) zone[0] = "BST"; } return zone[0].substring (0, 3); } String Time::getMonthName (const bool threeLetterVersion) const { return getMonthName (getMonth(), threeLetterVersion); } String Time::getWeekdayName (const bool threeLetterVersion) const { return getWeekdayName (getDayOfWeek(), threeLetterVersion); } String Time::getMonthName (int monthNumber, const bool threeLetterVersion) { const char* const shortMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; const char* const longMonthNames[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; monthNumber %= 12; return TRANS (threeLetterVersion ? shortMonthNames [monthNumber] : longMonthNames [monthNumber]); } String Time::getWeekdayName (int day, const bool threeLetterVersion) { const char* const shortDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char* const longDayNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; day %= 7; return TRANS (threeLetterVersion ? shortDayNames [day] : longDayNames [day]); } Time& Time::operator+= (const RelativeTime& delta) { millisSinceEpoch += delta.inMilliseconds(); return *this; } Time& Time::operator-= (const RelativeTime& delta) { millisSinceEpoch -= delta.inMilliseconds(); return *this; } Time operator+ (const Time& time, const RelativeTime& delta) { Time t (time); return t += delta; } Time operator- (const Time& time, const RelativeTime& delta) { Time t (time); return t -= delta; } Time operator+ (const RelativeTime& delta, const Time& time) { Time t (time); return t += delta; } const RelativeTime operator- (const Time& time1, const Time& time2) { return RelativeTime::milliseconds (time1.toMilliseconds() - time2.toMilliseconds()); } bool operator== (const Time& time1, const Time& time2) { return time1.toMilliseconds() == time2.toMilliseconds(); } bool operator!= (const Time& time1, const Time& time2) { return time1.toMilliseconds() != time2.toMilliseconds(); } bool operator< (const Time& time1, const Time& time2) { return time1.toMilliseconds() < time2.toMilliseconds(); } bool operator> (const Time& time1, const Time& time2) { return time1.toMilliseconds() > time2.toMilliseconds(); } bool operator<= (const Time& time1, const Time& time2) { return time1.toMilliseconds() <= time2.toMilliseconds(); } bool operator>= (const Time& time1, const Time& time2) { return time1.toMilliseconds() >= time2.toMilliseconds(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Time.cpp ***/ /*** Start of inlined file: juce_Initialisation.cpp ***/ BEGIN_JUCE_NAMESPACE #if ! JUCE_ONLY_BUILD_CORE_LIBRARY #endif #if ! JUCE_ONLY_BUILD_CORE_LIBRARY static bool juceInitialisedGUI = false; JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() { if (! juceInitialisedGUI) { juceInitialisedGUI = true; JUCE_AUTORELEASEPOOL MessageManager::getInstance(); } } JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI() { if (juceInitialisedGUI) { juceInitialisedGUI = false; JUCE_AUTORELEASEPOOL DeletedAtShutdown::deleteAll(); delete MessageManager::getInstance(); } } #endif #if JUCE_UNIT_TESTS class AtomicTests : public UnitTest { public: AtomicTests() : UnitTest ("Atomics") {} void runTest() { beginTest ("Misc"); char a1[7]; expect (numElementsInArray(a1) == 7); int a2[3]; expect (numElementsInArray(a2) == 3); expect (ByteOrder::swap ((uint16) 0x1122) == 0x2211); expect (ByteOrder::swap ((uint32) 0x11223344) == 0x44332211); expect (ByteOrder::swap ((uint64) literal64bit (0x1122334455667788)) == literal64bit (0x8877665544332211)); beginTest ("Atomic int"); AtomicTester ::testInteger (*this); beginTest ("Atomic unsigned int"); AtomicTester ::testInteger (*this); beginTest ("Atomic int32"); AtomicTester ::testInteger (*this); beginTest ("Atomic uint32"); AtomicTester ::testInteger (*this); beginTest ("Atomic long"); AtomicTester ::testInteger (*this); beginTest ("Atomic void*"); AtomicTester ::testInteger (*this); beginTest ("Atomic int*"); AtomicTester ::testInteger (*this); beginTest ("Atomic float"); AtomicTester ::testFloat (*this); #if ! JUCE_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms beginTest ("Atomic int64"); AtomicTester ::testInteger (*this); beginTest ("Atomic uint64"); AtomicTester ::testInteger (*this); beginTest ("Atomic double"); AtomicTester ::testFloat (*this); #endif } template class AtomicTester { public: AtomicTester() {} static void testInteger (UnitTest& test) { Atomic a, b; a.set ((Type) 10); test.expect (a.value == (Type) 10); test.expect (a.get() == (Type) 10); a += (Type) 15; test.expect (a.get() == (Type) 25); a.memoryBarrier(); a -= (Type) 5; test.expect (a.get() == (Type) 20); test.expect (++a == (Type) 21); ++a; test.expect (--a == (Type) 21); test.expect (a.get() == (Type) 21); a.memoryBarrier(); testFloat (test); } static void testFloat (UnitTest& test) { Atomic a, b; a = (Type) 21; a.memoryBarrier(); /* These are some simple test cases to check the atomics - let me know if any of these assertions fail on your system! */ test.expect (a.get() == (Type) 21); test.expect (a.compareAndSetValue ((Type) 100, (Type) 50) == (Type) 21); test.expect (a.get() == (Type) 21); test.expect (a.compareAndSetValue ((Type) 101, a.get()) == (Type) 21); test.expect (a.get() == (Type) 101); test.expect (! a.compareAndSetBool ((Type) 300, (Type) 200)); test.expect (a.get() == (Type) 101); test.expect (a.compareAndSetBool ((Type) 200, a.get())); test.expect (a.get() == (Type) 200); test.expect (a.exchange ((Type) 300) == (Type) 200); test.expect (a.get() == (Type) 300); b = a; test.expect (b.get() == a.get()); } }; }; static AtomicTests atomicUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Initialisation.cpp ***/ /*** Start of inlined file: juce_AbstractFifo.cpp ***/ BEGIN_JUCE_NAMESPACE AbstractFifo::AbstractFifo (const int capacity) noexcept : bufferSize (capacity) { jassert (bufferSize > 0); } AbstractFifo::~AbstractFifo() {} int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady(); } int AbstractFifo::getNumReady() const noexcept { const int vs = validStart.get(); const int ve = validEnd.get(); return ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); } void AbstractFifo::reset() noexcept { validEnd = 0; validStart = 0; } void AbstractFifo::setTotalSize (int newSize) noexcept { jassert (newSize > 0); reset(); bufferSize = newSize; } void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept { const int vs = validStart.get(); const int ve = validEnd.value; const int freeSpace = ve >= vs ? (bufferSize - (ve - vs)) : (vs - ve); numToWrite = jmin (numToWrite, freeSpace - 1); if (numToWrite <= 0) { startIndex1 = 0; startIndex2 = 0; blockSize1 = 0; blockSize2 = 0; } else { startIndex1 = ve; startIndex2 = 0; blockSize1 = jmin (bufferSize - ve, numToWrite); numToWrite -= blockSize1; blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, vs); } } void AbstractFifo::finishedWrite (int numWritten) noexcept { jassert (numWritten >= 0 && numWritten < bufferSize); int newEnd = validEnd.value + numWritten; if (newEnd >= bufferSize) newEnd -= bufferSize; validEnd = newEnd; } void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept { const int vs = validStart.value; const int ve = validEnd.get(); const int numReady = ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); numWanted = jmin (numWanted, numReady); if (numWanted <= 0) { startIndex1 = 0; startIndex2 = 0; blockSize1 = 0; blockSize2 = 0; } else { startIndex1 = vs; startIndex2 = 0; blockSize1 = jmin (bufferSize - vs, numWanted); numWanted -= blockSize1; blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, ve); } } void AbstractFifo::finishedRead (int numRead) noexcept { jassert (numRead >= 0 && numRead <= bufferSize); int newStart = validStart.value + numRead; if (newStart >= bufferSize) newStart -= bufferSize; validStart = newStart; } #if JUCE_UNIT_TESTS class AbstractFifoTests : public UnitTest { public: AbstractFifoTests() : UnitTest ("Abstract Fifo") {} class WriteThread : public Thread { public: WriteThread (AbstractFifo& fifo_, int* buffer_) : Thread ("fifo writer"), fifo (fifo_), buffer (buffer_) { startThread(); } ~WriteThread() { stopThread (5000); } void run() { int n = 0; while (! threadShouldExit()) { int num = Random::getSystemRandom().nextInt (2000) + 1; int start1, size1, start2, size2; fifo.prepareToWrite (num, start1, size1, start2, size2); jassert (size1 >= 0 && size2 >= 0); jassert (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())); jassert (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize())); int i; for (i = 0; i < size1; ++i) buffer [start1 + i] = n++; for (i = 0; i < size2; ++i) buffer [start2 + i] = n++; fifo.finishedWrite (size1 + size2); } } private: AbstractFifo& fifo; int* buffer; }; void runTest() { beginTest ("AbstractFifo"); int buffer [5000]; AbstractFifo fifo (numElementsInArray (buffer)); WriteThread writer (fifo, buffer); int n = 0; for (int count = 1000000; --count >= 0;) { int num = Random::getSystemRandom().nextInt (6000) + 1; int start1, size1, start2, size2; fifo.prepareToRead (num, start1, size1, start2, size2); if (! (size1 >= 0 && size2 >= 0) && (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())) && (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize()))) { expect (false, "prepareToRead returned -ve values"); break; } bool failed = false; int i; for (i = 0; i < size1; ++i) failed = (buffer [start1 + i] != n++) || failed; for (i = 0; i < size2; ++i) failed = (buffer [start2 + i] != n++) || failed; if (failed) { expect (false, "read values were incorrect"); break; } fifo.finishedRead (size1 + size2); } } }; static AbstractFifoTests fifoUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_AbstractFifo.cpp ***/ /*** Start of inlined file: juce_BigInteger.cpp ***/ BEGIN_JUCE_NAMESPACE BigInteger::BigInteger() : numValues (4), highestBit (-1), negative (false) { values.calloc (numValues + 1); } BigInteger::BigInteger (const int32 value) : numValues (4), highestBit (31), negative (value < 0) { values.calloc (numValues + 1); values[0] = abs (value); highestBit = getHighestBit(); } BigInteger::BigInteger (const uint32 value) : numValues (4), highestBit (31), negative (false) { values.calloc (numValues + 1); values[0] = value; highestBit = getHighestBit(); } BigInteger::BigInteger (int64 value) : numValues (4), highestBit (63), negative (value < 0) { values.calloc (numValues + 1); if (value < 0) value = -value; values[0] = (uint32) value; values[1] = (uint32) (value >> 32); highestBit = getHighestBit(); } BigInteger::BigInteger (const BigInteger& other) : numValues (jmax (4, bitToIndex (other.highestBit) + 1)), highestBit (other.getHighestBit()), negative (other.negative) { values.malloc (numValues + 1); memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); } BigInteger::~BigInteger() { } void BigInteger::swapWith (BigInteger& other) noexcept { values.swapWith (other.values); std::swap (numValues, other.numValues); std::swap (highestBit, other.highestBit); std::swap (negative, other.negative); } BigInteger& BigInteger::operator= (const BigInteger& other) { if (this != &other) { highestBit = other.getHighestBit(); numValues = jmax (4, bitToIndex (highestBit) + 1); negative = other.negative; values.malloc (numValues + 1); memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); } return *this; } void BigInteger::ensureSize (const int numVals) { if (numVals + 2 >= numValues) { int oldSize = numValues; numValues = ((numVals + 2) * 3) / 2; values.realloc (numValues + 1); while (oldSize < numValues) values [oldSize++] = 0; } } bool BigInteger::operator[] (const int bit) const noexcept { return bit <= highestBit && bit >= 0 && ((values [bitToIndex (bit)] & bitToMask (bit)) != 0); } int BigInteger::toInteger() const noexcept { const int n = (int) (values[0] & 0x7fffffff); return negative ? -n : n; } BigInteger BigInteger::getBitRange (int startBit, int numBits) const { BigInteger r; numBits = jmin (numBits, getHighestBit() + 1 - startBit); r.ensureSize (bitToIndex (numBits)); r.highestBit = numBits; int i = 0; while (numBits > 0) { r.values[i++] = getBitRangeAsInt (startBit, jmin (32, numBits)); numBits -= 32; startBit += 32; } r.highestBit = r.getHighestBit(); return r; } int BigInteger::getBitRangeAsInt (const int startBit, int numBits) const noexcept { if (numBits > 32) { jassertfalse; // use getBitRange() if you need more than 32 bits.. numBits = 32; } numBits = jmin (numBits, highestBit + 1 - startBit); if (numBits <= 0) return 0; const int pos = bitToIndex (startBit); const int offset = startBit & 31; const int endSpace = 32 - numBits; uint32 n = ((uint32) values [pos]) >> offset; if (offset > endSpace) n |= ((uint32) values [pos + 1]) << (32 - offset); return (int) (n & (((uint32) 0xffffffff) >> endSpace)); } void BigInteger::setBitRangeAsInt (const int startBit, int numBits, uint32 valueToSet) { if (numBits > 32) { jassertfalse; numBits = 32; } for (int i = 0; i < numBits; ++i) { setBit (startBit + i, (valueToSet & 1) != 0); valueToSet >>= 1; } } void BigInteger::clear() { if (numValues > 16) { numValues = 4; values.calloc (numValues + 1); } else { values.clear (numValues + 1); } highestBit = -1; negative = false; } void BigInteger::setBit (const int bit) { if (bit >= 0) { if (bit > highestBit) { ensureSize (bitToIndex (bit)); highestBit = bit; } values [bitToIndex (bit)] |= bitToMask (bit); } } void BigInteger::setBit (const int bit, const bool shouldBeSet) { if (shouldBeSet) setBit (bit); else clearBit (bit); } void BigInteger::clearBit (const int bit) noexcept { if (bit >= 0 && bit <= highestBit) values [bitToIndex (bit)] &= ~bitToMask (bit); } void BigInteger::setRange (int startBit, int numBits, const bool shouldBeSet) { while (--numBits >= 0) setBit (startBit++, shouldBeSet); } void BigInteger::insertBit (const int bit, const bool shouldBeSet) { if (bit >= 0) shiftBits (1, bit); setBit (bit, shouldBeSet); } bool BigInteger::isZero() const noexcept { return getHighestBit() < 0; } bool BigInteger::isOne() const noexcept { return getHighestBit() == 0 && ! negative; } bool BigInteger::isNegative() const noexcept { return negative && ! isZero(); } void BigInteger::setNegative (const bool neg) noexcept { negative = neg; } void BigInteger::negate() noexcept { negative = (! negative) && ! isZero(); } #if JUCE_USE_INTRINSICS #pragma intrinsic (_BitScanReverse) #endif namespace BitFunctions { inline int countBitsInInt32 (uint32 n) noexcept { n -= ((n >> 1) & 0x55555555); n = (((n >> 2) & 0x33333333) + (n & 0x33333333)); n = (((n >> 4) + n) & 0x0f0f0f0f); n += (n >> 8); n += (n >> 16); return n & 0x3f; } inline int highestBitInInt (uint32 n) noexcept { jassert (n != 0); // (the built-in functions may not work for n = 0) #if JUCE_GCC return 31 - __builtin_clz (n); #elif JUCE_USE_INTRINSICS unsigned long highest; _BitScanReverse (&highest, n); return (int) highest; #else n |= (n >> 1); n |= (n >> 2); n |= (n >> 4); n |= (n >> 8); n |= (n >> 16); return countBitsInInt32 (n >> 1); #endif } } int BigInteger::countNumberOfSetBits() const noexcept { int total = 0; for (int i = bitToIndex (highestBit) + 1; --i >= 0;) total += BitFunctions::countBitsInInt32 (values[i]); return total; } int BigInteger::getHighestBit() const noexcept { for (int i = bitToIndex (highestBit + 1); i >= 0; --i) { const uint32 n = values[i]; if (n != 0) return BitFunctions::highestBitInInt (n) + (i << 5); } return -1; } int BigInteger::findNextSetBit (int i) const noexcept { for (; i <= highestBit; ++i) if ((values [bitToIndex (i)] & bitToMask (i)) != 0) return i; return -1; } int BigInteger::findNextClearBit (int i) const noexcept { for (; i <= highestBit; ++i) if ((values [bitToIndex (i)] & bitToMask (i)) == 0) break; return i; } BigInteger& BigInteger::operator+= (const BigInteger& other) { if (other.isNegative()) return operator-= (-other); if (isNegative()) { if (compareAbsolute (other) < 0) { BigInteger temp (*this); temp.negate(); *this = other; operator-= (temp); } else { negate(); operator-= (other); negate(); } } else { if (other.highestBit > highestBit) highestBit = other.highestBit; ++highestBit; const int numInts = bitToIndex (highestBit) + 1; ensureSize (numInts); int64 remainder = 0; for (int i = 0; i <= numInts; ++i) { if (i < numValues) remainder += values[i]; if (i < other.numValues) remainder += other.values[i]; values[i] = (uint32) remainder; remainder >>= 32; } jassert (remainder == 0); highestBit = getHighestBit(); } return *this; } BigInteger& BigInteger::operator-= (const BigInteger& other) { if (other.isNegative()) return operator+= (-other); if (! isNegative()) { if (compareAbsolute (other) < 0) { BigInteger temp (other); swapWith (temp); operator-= (temp); negate(); return *this; } } else { negate(); operator+= (other); negate(); return *this; } const int numInts = bitToIndex (highestBit) + 1; const int maxOtherInts = bitToIndex (other.highestBit) + 1; int64 amountToSubtract = 0; for (int i = 0; i <= numInts; ++i) { if (i <= maxOtherInts) amountToSubtract += (int64) other.values[i]; if (values[i] >= amountToSubtract) { values[i] = (uint32) (values[i] - amountToSubtract); amountToSubtract = 0; } else { const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; values[i] = (uint32) n; amountToSubtract = 1; } } return *this; } BigInteger& BigInteger::operator*= (const BigInteger& other) { BigInteger total; highestBit = getHighestBit(); const bool wasNegative = isNegative(); setNegative (false); for (int i = 0; i <= highestBit; ++i) { if (operator[](i)) { BigInteger n (other); n.setNegative (false); n <<= i; total += n; } } total.setNegative (wasNegative ^ other.isNegative()); swapWith (total); return *this; } void BigInteger::divideBy (const BigInteger& divisor, BigInteger& remainder) { jassert (this != &remainder); // (can't handle passing itself in to get the remainder) const int divHB = divisor.getHighestBit(); const int ourHB = getHighestBit(); if (divHB < 0 || ourHB < 0) { // division by zero remainder.clear(); clear(); } else { const bool wasNegative = isNegative(); swapWith (remainder); remainder.setNegative (false); clear(); BigInteger temp (divisor); temp.setNegative (false); int leftShift = ourHB - divHB; temp <<= leftShift; while (leftShift >= 0) { if (remainder.compareAbsolute (temp) >= 0) { remainder -= temp; setBit (leftShift); } if (--leftShift >= 0) temp >>= 1; } negative = wasNegative ^ divisor.isNegative(); remainder.setNegative (wasNegative); } } BigInteger& BigInteger::operator/= (const BigInteger& other) { BigInteger remainder; divideBy (other, remainder); return *this; } BigInteger& BigInteger::operator|= (const BigInteger& other) { // this operation doesn't take into account negative values.. jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { ensureSize (bitToIndex (other.highestBit)); int n = bitToIndex (other.highestBit) + 1; while (--n >= 0) values[n] |= other.values[n]; if (other.highestBit > highestBit) highestBit = other.highestBit; highestBit = getHighestBit(); } return *this; } BigInteger& BigInteger::operator&= (const BigInteger& other) { // this operation doesn't take into account negative values.. jassert (isNegative() == other.isNegative()); int n = numValues; while (n > other.numValues) values[--n] = 0; while (--n >= 0) values[n] &= other.values[n]; if (other.highestBit < highestBit) highestBit = other.highestBit; highestBit = getHighestBit(); return *this; } BigInteger& BigInteger::operator^= (const BigInteger& other) { // this operation will only work with the absolute values jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { ensureSize (bitToIndex (other.highestBit)); int n = bitToIndex (other.highestBit) + 1; while (--n >= 0) values[n] ^= other.values[n]; if (other.highestBit > highestBit) highestBit = other.highestBit; highestBit = getHighestBit(); } return *this; } BigInteger& BigInteger::operator%= (const BigInteger& divisor) { BigInteger remainder; divideBy (divisor, remainder); swapWith (remainder); return *this; } BigInteger& BigInteger::operator++() { return operator+= (1); } BigInteger& BigInteger::operator--() { return operator-= (1); } BigInteger BigInteger::operator++ (int) { const BigInteger old (*this); operator+= (1); return old; } BigInteger BigInteger::operator-- (int) { const BigInteger old (*this); operator-= (1); return old; } BigInteger BigInteger::operator-() const { BigInteger b (*this); b.negate(); return b; } BigInteger BigInteger::operator+ (const BigInteger& other) const { BigInteger b (*this); return b += other; } BigInteger BigInteger::operator- (const BigInteger& other) const { BigInteger b (*this); return b -= other; } BigInteger BigInteger::operator* (const BigInteger& other) const { BigInteger b (*this); return b *= other; } BigInteger BigInteger::operator/ (const BigInteger& other) const { BigInteger b (*this); return b /= other; } BigInteger BigInteger::operator| (const BigInteger& other) const { BigInteger b (*this); return b |= other; } BigInteger BigInteger::operator& (const BigInteger& other) const { BigInteger b (*this); return b &= other; } BigInteger BigInteger::operator^ (const BigInteger& other) const { BigInteger b (*this); return b ^= other; } BigInteger BigInteger::operator% (const BigInteger& other) const { BigInteger b (*this); return b %= other; } BigInteger BigInteger::operator<< (const int numBits) const { BigInteger b (*this); return b <<= numBits; } BigInteger BigInteger::operator>> (const int numBits) const { BigInteger b (*this); return b >>= numBits; } BigInteger& BigInteger::operator<<= (const int numBits) { shiftBits (numBits, 0); return *this; } BigInteger& BigInteger::operator>>= (const int numBits) { shiftBits (-numBits, 0); return *this; } int BigInteger::compare (const BigInteger& other) const noexcept { if (isNegative() == other.isNegative()) { const int absComp = compareAbsolute (other); return isNegative() ? -absComp : absComp; } else { return isNegative() ? -1 : 1; } } int BigInteger::compareAbsolute (const BigInteger& other) const noexcept { const int h1 = getHighestBit(); const int h2 = other.getHighestBit(); if (h1 > h2) return 1; else if (h1 < h2) return -1; for (int i = bitToIndex (h1) + 1; --i >= 0;) if (values[i] != other.values[i]) return (values[i] > other.values[i]) ? 1 : -1; return 0; } bool BigInteger::operator== (const BigInteger& other) const noexcept { return compare (other) == 0; } bool BigInteger::operator!= (const BigInteger& other) const noexcept { return compare (other) != 0; } bool BigInteger::operator< (const BigInteger& other) const noexcept { return compare (other) < 0; } bool BigInteger::operator<= (const BigInteger& other) const noexcept { return compare (other) <= 0; } bool BigInteger::operator> (const BigInteger& other) const noexcept { return compare (other) > 0; } bool BigInteger::operator>= (const BigInteger& other) const noexcept { return compare (other) >= 0; } void BigInteger::shiftLeft (int bits, const int startBit) { if (startBit > 0) { for (int i = highestBit + 1; --i >= startBit;) setBit (i + bits, operator[] (i)); while (--bits >= 0) clearBit (bits + startBit); } else { ensureSize (bitToIndex (highestBit + bits) + 1); const int wordsToMove = bitToIndex (bits); int top = 1 + bitToIndex (highestBit); highestBit += bits; if (wordsToMove > 0) { int i; for (i = top; --i >= 0;) values [i + wordsToMove] = values [i]; for (i = 0; i < wordsToMove; ++i) values [i] = 0; bits &= 31; } if (bits != 0) { const int invBits = 32 - bits; for (int i = top + 1 + wordsToMove; --i > wordsToMove;) values[i] = (values[i] << bits) | (values [i - 1] >> invBits); values [wordsToMove] = values [wordsToMove] << bits; } highestBit = getHighestBit(); } } void BigInteger::shiftRight (int bits, const int startBit) { if (startBit > 0) { for (int i = startBit; i <= highestBit; ++i) setBit (i, operator[] (i + bits)); highestBit = getHighestBit(); } else { if (bits > highestBit) { clear(); } else { const int wordsToMove = bitToIndex (bits); int top = 1 + bitToIndex (highestBit) - wordsToMove; highestBit -= bits; if (wordsToMove > 0) { int i; for (i = 0; i < top; ++i) values [i] = values [i + wordsToMove]; for (i = 0; i < wordsToMove; ++i) values [top + i] = 0; bits &= 31; } if (bits != 0) { const int invBits = 32 - bits; --top; for (int i = 0; i < top; ++i) values[i] = (values[i] >> bits) | (values [i + 1] << invBits); values[top] = (values[top] >> bits); } highestBit = getHighestBit(); } } } void BigInteger::shiftBits (int bits, const int startBit) { if (highestBit >= 0) { if (bits < 0) shiftRight (-bits, startBit); else if (bits > 0) shiftLeft (bits, startBit); } } BigInteger BigInteger::simpleGCD (BigInteger* m, BigInteger* n) { while (! m->isZero()) { if (n->compareAbsolute (*m) > 0) std::swap (m, n); *m -= *n; } return *n; } BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const { BigInteger m (*this); while (! n.isZero()) { if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) return simpleGCD (&m, &n); BigInteger temp1 (m), temp2; temp1.divideBy (n, temp2); m = n; n = temp2; } return m; } void BigInteger::exponentModulo (const BigInteger& exponent, const BigInteger& modulus) { BigInteger exp (exponent); exp %= modulus; BigInteger value (1); swapWith (value); value %= modulus; while (! exp.isZero()) { if (exp [0]) { operator*= (value); operator%= (modulus); } value *= value; value %= modulus; exp >>= 1; } } void BigInteger::inverseModulo (const BigInteger& modulus) { if (modulus.isOne() || modulus.isNegative()) { clear(); return; } if (isNegative() || compareAbsolute (modulus) >= 0) operator%= (modulus); if (isOne()) return; if (! (*this)[0]) { // not invertible clear(); return; } BigInteger a1 (modulus); BigInteger a2 (*this); BigInteger b1 (modulus); BigInteger b2 (1); while (! a2.isOne()) { BigInteger temp1, temp2, multiplier (a1); multiplier.divideBy (a2, temp1); temp1 = a2; temp1 *= multiplier; temp2 = a1; temp2 -= temp1; a1 = a2; a2 = temp2; temp1 = b2; temp1 *= multiplier; temp2 = b1; temp2 -= temp1; b1 = b2; b2 = temp2; } while (b2.isNegative()) b2 += modulus; b2 %= modulus; swapWith (b2); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value) { return stream << value.toString (10); } String BigInteger::toString (const int base, const int minimumNumCharacters) const { String s; BigInteger v (*this); if (base == 2 || base == 8 || base == 16) { const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); static const char* const hexDigits = "0123456789abcdef"; for (;;) { const int remainder = v.getBitRangeAsInt (0, bits); v >>= bits; if (remainder == 0 && v.isZero()) break; s = String::charToString (hexDigits [remainder]) + s; } } else if (base == 10) { const BigInteger ten (10); BigInteger remainder; for (;;) { v.divideBy (ten, remainder); if (remainder.isZero() && v.isZero()) break; s = String (remainder.getBitRangeAsInt (0, 8)) + s; } } else { jassertfalse; // can't do the specified base! return String::empty; } s = s.paddedLeft ('0', minimumNumCharacters); return isNegative() ? "-" + s : s; } void BigInteger::parseString (const String& text, const int base) { clear(); String::CharPointerType t (text.getCharPointer()); if (base == 2 || base == 8 || base == 16) { const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); for (;;) { const juce_wchar c = t.getAndAdvance(); const int digit = CharacterFunctions::getHexDigitValue (c); if (((uint32) digit) < (uint32) base) { operator<<= (bits); operator+= (digit); } else if (c == 0) { break; } } } else if (base == 10) { const BigInteger ten ((uint32) 10); for (;;) { const juce_wchar c = t.getAndAdvance(); if (c >= '0' && c <= '9') { operator*= (ten); operator+= ((int) (c - '0')); } else if (c == 0) { break; } } } setNegative (text.trimStart().startsWithChar ('-')); } MemoryBlock BigInteger::toMemoryBlock() const { const int numBytes = (getHighestBit() + 8) >> 3; MemoryBlock mb ((size_t) numBytes); for (int i = 0; i < numBytes; ++i) mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); return mb; } void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) { clear(); for (int i = (int) data.getSize(); --i >= 0;) this->setBitRangeAsInt ((int) (i << 3), 8, data [i]); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BigInteger.cpp ***/ /*** Start of inlined file: juce_MemoryBlock.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryBlock::MemoryBlock() noexcept : size (0) { } MemoryBlock::MemoryBlock (const size_t initialSize, const bool initialiseToZero) { if (initialSize > 0) { size = initialSize; data.allocate (initialSize, initialiseToZero); } else { size = 0; } } MemoryBlock::MemoryBlock (const MemoryBlock& other) : size (other.size) { if (size > 0) { jassert (other.data != nullptr); data.malloc (size); memcpy (data, other.data, size); } } MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) : size (jmax ((size_t) 0, sizeInBytes)) { jassert (sizeInBytes >= 0); if (size > 0) { jassert (dataToInitialiseFrom != nullptr); // non-zero size, but a zero pointer passed-in? data.malloc (size); if (dataToInitialiseFrom != nullptr) memcpy (data, dataToInitialiseFrom, size); } } MemoryBlock::~MemoryBlock() noexcept { jassert (size >= 0); // should never happen jassert (size == 0 || data != nullptr); // non-zero size but no data allocated? } MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) { if (this != &other) { setSize (other.size, false); memcpy (data, other.data, size); } return *this; } bool MemoryBlock::operator== (const MemoryBlock& other) const noexcept { return matches (other.data, other.size); } bool MemoryBlock::operator!= (const MemoryBlock& other) const noexcept { return ! operator== (other); } bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const noexcept { return size == dataSize && memcmp (data, dataToCompare, size) == 0; } // this will resize the block to this size void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) { if (size != newSize) { if (newSize <= 0) { data.free(); size = 0; } else { if (data != nullptr) { data.realloc (newSize); if (initialiseToZero && (newSize > size)) zeromem (data + size, newSize - size); } else { data.allocate (newSize, initialiseToZero); } size = newSize; } } } void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) { if (size < minimumSize) setSize (minimumSize, initialiseToZero); } void MemoryBlock::swapWith (MemoryBlock& other) noexcept { std::swap (size, other.size); data.swapWith (other.data); } void MemoryBlock::fillWith (const uint8 value) noexcept { memset (data, (int) value, size); } void MemoryBlock::append (const void* const srcData, const size_t numBytes) { if (numBytes > 0) { const size_t oldSize = size; setSize (size + numBytes); memcpy (data + oldSize, srcData, numBytes); } } void MemoryBlock::copyFrom (const void* const src, int offset, size_t num) noexcept { const char* d = static_cast (src); if (offset < 0) { d -= offset; num -= offset; offset = 0; } if (offset + num > size) num = size - offset; if (num > 0) memcpy (data + offset, d, num); } void MemoryBlock::copyTo (void* const dst, int offset, size_t num) const noexcept { char* d = static_cast (dst); if (offset < 0) { zeromem (d, -offset); d -= offset; num += offset; offset = 0; } if (offset + num > size) { const size_t newNum = size - offset; zeromem (d + newNum, num - newNum); num = newNum; } if (num > 0) memcpy (d, data + offset, num); } void MemoryBlock::removeSection (size_t startByte, size_t numBytesToRemove) { if (startByte + numBytesToRemove >= size) { setSize (startByte); } else if (numBytesToRemove > 0) { memmove (data + startByte, data + startByte + numBytesToRemove, size - (startByte + numBytesToRemove)); setSize (size - numBytesToRemove); } } const String MemoryBlock::toString() const { return String (static_cast (getData()), size); } int MemoryBlock::getBitRange (const size_t bitRangeStart, size_t numBits) const noexcept { int res = 0; size_t byte = bitRangeStart >> 3; int offsetInByte = (int) bitRangeStart & 7; size_t bitsSoFar = 0; while (numBits > 0 && (size_t) byte < size) { const int bitsThisTime = jmin ((int) numBits, 8 - offsetInByte); const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; res |= (((data[byte] & mask) >> offsetInByte) << bitsSoFar); bitsSoFar += bitsThisTime; numBits -= bitsThisTime; ++byte; offsetInByte = 0; } return res; } void MemoryBlock::setBitRange (const size_t bitRangeStart, size_t numBits, int bitsToSet) noexcept { size_t byte = bitRangeStart >> 3; int offsetInByte = (int) bitRangeStart & 7; unsigned int mask = ~((((unsigned int) 0xffffffff) << (32 - numBits)) >> (32 - numBits)); while (numBits > 0 && (size_t) byte < size) { const int bitsThisTime = jmin ((int) numBits, 8 - offsetInByte); const unsigned int tempMask = (mask << offsetInByte) | ~((((unsigned int) 0xffffffff) >> offsetInByte) << offsetInByte); const unsigned int tempBits = bitsToSet << offsetInByte; data[byte] = (char) ((data[byte] & tempMask) | tempBits); ++byte; numBits -= bitsThisTime; bitsToSet >>= bitsThisTime; mask >>= bitsThisTime; offsetInByte = 0; } } void MemoryBlock::loadFromHexString (const String& hex) { ensureSize (hex.length() >> 1); char* dest = data; String::CharPointerType t (hex.getCharPointer()); for (;;) { int byte = 0; for (int loop = 2; --loop >= 0;) { byte <<= 4; for (;;) { const juce_wchar c = t.getAndAdvance(); if (c >= '0' && c <= '9') { byte |= c - '0'; break; } else if (c >= 'a' && c <= 'z') { byte |= c - ('a' - 10); break; } else if (c >= 'A' && c <= 'Z') { byte |= c - ('A' - 10); break; } else if (c == 0) { setSize (static_cast (dest - data)); return; } } } *dest++ = (char) byte; } } const char* const MemoryBlock::encodingTable = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; const String MemoryBlock::toBase64Encoding() const { const size_t numChars = ((size << 3) + 5) / 6; String destString ((unsigned int) size); // store the length, followed by a '.', and then the data. const int initialLen = destString.length(); destString.preallocateBytes (sizeof (String::CharPointerType::CharType) * (initialLen + 2 + numChars)); String::CharPointerType d (destString.getCharPointer()); d += initialLen; d.write ('.'); for (size_t i = 0; i < numChars; ++i) d.write (encodingTable [getBitRange (i * 6, 6)]); d.writeNull(); return destString; } bool MemoryBlock::fromBase64Encoding (const String& s) { const int startPos = s.indexOfChar ('.') + 1; if (startPos <= 0) return false; const int numBytesNeeded = s.substring (0, startPos - 1).getIntValue(); setSize (numBytesNeeded, true); const int numChars = s.length() - startPos; String::CharPointerType srcChars (s.getCharPointer()); srcChars += startPos; int pos = 0; for (int i = 0; i < numChars; ++i) { const char c = (char) srcChars.getAndAdvance(); for (int j = 0; j < 64; ++j) { if (encodingTable[j] == c) { setBitRange (pos, 6, j); pos += 6; break; } } } return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryBlock.cpp ***/ /*** Start of inlined file: juce_PropertySet.cpp ***/ BEGIN_JUCE_NAMESPACE PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) : properties (ignoreCaseOfKeyNames), fallbackProperties (nullptr), ignoreCaseOfKeys (ignoreCaseOfKeyNames) { } PropertySet::PropertySet (const PropertySet& other) : properties (other.properties), fallbackProperties (other.fallbackProperties), ignoreCaseOfKeys (other.ignoreCaseOfKeys) { } PropertySet& PropertySet::operator= (const PropertySet& other) { properties = other.properties; fallbackProperties = other.fallbackProperties; ignoreCaseOfKeys = other.ignoreCaseOfKeys; propertyChanged(); return *this; } PropertySet::~PropertySet() { } void PropertySet::clear() { const ScopedLock sl (lock); if (properties.size() > 0) { properties.clear(); propertyChanged(); } } String PropertySet::getValue (const String& keyName, const String& defaultValue) const noexcept { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index]; return fallbackProperties != nullptr ? fallbackProperties->getValue (keyName, defaultValue) : defaultValue; } int PropertySet::getIntValue (const String& keyName, const int defaultValue) const noexcept { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue(); return fallbackProperties != nullptr ? fallbackProperties->getIntValue (keyName, defaultValue) : defaultValue; } double PropertySet::getDoubleValue (const String& keyName, const double defaultValue) const noexcept { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues()[index].getDoubleValue(); return fallbackProperties != nullptr ? fallbackProperties->getDoubleValue (keyName, defaultValue) : defaultValue; } bool PropertySet::getBoolValue (const String& keyName, const bool defaultValue) const noexcept { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue() != 0; return fallbackProperties != nullptr ? fallbackProperties->getBoolValue (keyName, defaultValue) : defaultValue; } XmlElement* PropertySet::getXmlValue (const String& keyName) const { return XmlDocument::parse (getValue (keyName)); } void PropertySet::setValue (const String& keyName, const var& v) { jassert (keyName.isNotEmpty()); // shouldn't use an empty key name! if (keyName.isNotEmpty()) { const String value (v.toString()); const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index < 0 || properties.getAllValues() [index] != value) { properties.set (keyName, value); propertyChanged(); } } } void PropertySet::removeValue (const String& keyName) { if (keyName.isNotEmpty()) { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) { properties.remove (keyName); propertyChanged(); } } } void PropertySet::setValue (const String& keyName, const XmlElement* const xml) { setValue (keyName, xml == nullptr ? var::null : var (xml->createDocument (String::empty, true))); } bool PropertySet::containsKey (const String& keyName) const noexcept { const ScopedLock sl (lock); return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); } void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept { const ScopedLock sl (lock); fallbackProperties = fallbackProperties_; } XmlElement* PropertySet::createXml (const String& nodeName) const { const ScopedLock sl (lock); XmlElement* const xml = new XmlElement (nodeName); for (int i = 0; i < properties.getAllKeys().size(); ++i) { XmlElement* const e = xml->createNewChildElement ("VALUE"); e->setAttribute ("name", properties.getAllKeys()[i]); e->setAttribute ("val", properties.getAllValues()[i]); } return xml; } void PropertySet::restoreFromXml (const XmlElement& xml) { const ScopedLock sl (lock); clear(); forEachXmlChildElementWithTagName (xml, e, "VALUE") { if (e->hasAttribute ("name") && e->hasAttribute ("val")) { properties.set (e->getStringAttribute ("name"), e->getStringAttribute ("val")); } } if (properties.size() > 0) propertyChanged(); } void PropertySet::propertyChanged() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertySet.cpp ***/ /*** Start of inlined file: juce_Identifier.cpp ***/ BEGIN_JUCE_NAMESPACE StringPool& Identifier::getPool() { static StringPool pool; return pool; } Identifier::Identifier() noexcept : name (nullptr) { } Identifier::Identifier (const Identifier& other) noexcept : name (other.name) { } Identifier& Identifier::operator= (const Identifier& other) noexcept { name = other.name; return *this; } Identifier::Identifier (const String& name_) : name (Identifier::getPool().getPooledString (name_)) { /* An Identifier string must be suitable for use as a script variable or XML attribute, so it can only contain this limited set of characters.. */ jassert (isValidIdentifier (name_)); } Identifier::Identifier (const char* const name_) : name (Identifier::getPool().getPooledString (name_)) { /* An Identifier string must be suitable for use as a script variable or XML attribute, so it can only contain this limited set of characters.. */ jassert (isValidIdentifier (toString())); } Identifier::~Identifier() { } bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept { return possibleIdentifier.isNotEmpty() && possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Identifier.cpp ***/ /*** Start of inlined file: juce_Variant.cpp ***/ BEGIN_JUCE_NAMESPACE enum VariantStreamMarkers { varMarker_Int = 1, varMarker_BoolTrue = 2, varMarker_BoolFalse = 3, varMarker_Double = 4, varMarker_String = 5, varMarker_Int64 = 6, varMarker_Array = 7 }; class var::VariantType { public: VariantType() noexcept {} virtual ~VariantType() noexcept {} virtual int toInt (const ValueUnion&) const noexcept { return 0; } virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } virtual double toDouble (const ValueUnion&) const noexcept { return 0; } virtual String toString (const ValueUnion&) const { return String::empty; } virtual bool toBool (const ValueUnion&) const noexcept { return false; } virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } virtual Array* toArray (const ValueUnion&) const noexcept { return 0; } virtual bool isVoid() const noexcept { return false; } virtual bool isInt() const noexcept { return false; } virtual bool isInt64() const noexcept { return false; } virtual bool isBool() const noexcept { return false; } virtual bool isDouble() const noexcept { return false; } virtual bool isString() const noexcept { return false; } virtual bool isObject() const noexcept { return false; } virtual bool isArray() const noexcept { return false; } virtual bool isMethod() const noexcept { return false; } virtual void cleanUp (ValueUnion&) const noexcept {} virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; }; class var::VariantType_Void : public var::VariantType { public: VariantType_Void() noexcept {} static const VariantType_Void instance; bool isVoid() const noexcept { return true; } bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept { return otherType.isVoid(); } void writeToStream (const ValueUnion&, OutputStream& output) const { output.writeCompressedInt (0); } }; class var::VariantType_Int : public var::VariantType { public: VariantType_Int() noexcept {} static const VariantType_Int instance; int toInt (const ValueUnion& data) const noexcept { return data.intValue; }; int64 toInt64 (const ValueUnion& data) const noexcept { return (int64) data.intValue; }; double toDouble (const ValueUnion& data) const noexcept { return (double) data.intValue; } String toString (const ValueUnion& data) const { return String (data.intValue); } bool toBool (const ValueUnion& data) const noexcept { return data.intValue != 0; } bool isInt() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toInt (otherData) == data.intValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (5); output.writeByte (varMarker_Int); output.writeInt (data.intValue); } }; class var::VariantType_Int64 : public var::VariantType { public: VariantType_Int64() noexcept {} static const VariantType_Int64 instance; int toInt (const ValueUnion& data) const noexcept { return (int) data.int64Value; }; int64 toInt64 (const ValueUnion& data) const noexcept { return data.int64Value; }; double toDouble (const ValueUnion& data) const noexcept { return (double) data.int64Value; } String toString (const ValueUnion& data) const { return String (data.int64Value); } bool toBool (const ValueUnion& data) const noexcept { return data.int64Value != 0; } bool isInt64() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toInt64 (otherData) == data.int64Value; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (9); output.writeByte (varMarker_Int64); output.writeInt64 (data.int64Value); } }; class var::VariantType_Double : public var::VariantType { public: VariantType_Double() noexcept {} static const VariantType_Double instance; int toInt (const ValueUnion& data) const noexcept { return (int) data.doubleValue; }; int64 toInt64 (const ValueUnion& data) const noexcept { return (int64) data.doubleValue; }; double toDouble (const ValueUnion& data) const noexcept { return data.doubleValue; } String toString (const ValueUnion& data) const { return String (data.doubleValue); } bool toBool (const ValueUnion& data) const noexcept { return data.doubleValue != 0; } bool isDouble() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toDouble (otherData) == data.doubleValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (9); output.writeByte (varMarker_Double); output.writeDouble (data.doubleValue); } }; class var::VariantType_Bool : public var::VariantType { public: VariantType_Bool() noexcept {} static const VariantType_Bool instance; int toInt (const ValueUnion& data) const noexcept { return data.boolValue ? 1 : 0; }; int64 toInt64 (const ValueUnion& data) const noexcept { return data.boolValue ? 1 : 0; }; double toDouble (const ValueUnion& data) const noexcept { return data.boolValue ? 1.0 : 0.0; } String toString (const ValueUnion& data) const { return String::charToString (data.boolValue ? '1' : '0'); } bool toBool (const ValueUnion& data) const noexcept { return data.boolValue; } bool isBool() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toBool (otherData) == data.boolValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (1); output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); } }; class var::VariantType_String : public var::VariantType { public: VariantType_String() noexcept {} static const VariantType_String instance; void cleanUp (ValueUnion& data) const noexcept { getString (data)-> ~String(); } void createCopy (ValueUnion& dest, const ValueUnion& source) const { new (dest.stringValue) String (*getString (source)); } bool isString() const noexcept { return true; } int toInt (const ValueUnion& data) const noexcept { return getString (data)->getIntValue(); }; int64 toInt64 (const ValueUnion& data) const noexcept { return getString (data)->getLargeIntValue(); }; double toDouble (const ValueUnion& data) const noexcept { return getString (data)->getDoubleValue(); } String toString (const ValueUnion& data) const { return *getString (data); } bool toBool (const ValueUnion& data) const noexcept { return getString (data)->getIntValue() != 0 || getString (data)->trim().equalsIgnoreCase ("true") || getString (data)->trim().equalsIgnoreCase ("yes"); } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toString (otherData) == *getString (data); } void writeToStream (const ValueUnion& data, OutputStream& output) const { const String* const s = getString (data); const int len = s->getNumBytesAsUTF8() + 1; HeapBlock temp (len); s->copyToUTF8 (temp, len); output.writeCompressedInt (len + 1); output.writeByte (varMarker_String); output.write (temp, len); } private: static inline const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } }; class var::VariantType_Object : public var::VariantType { public: VariantType_Object() noexcept {} static const VariantType_Object instance; void cleanUp (ValueUnion& data) const noexcept { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.objectValue = source.objectValue; if (dest.objectValue != nullptr) dest.objectValue->incReferenceCount(); } String toString (const ValueUnion& data) const { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } bool toBool (const ValueUnion& data) const noexcept { return data.objectValue != 0; } ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept { return data.objectValue; } bool isObject() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.toObject (otherData) == data.objectValue; } void writeToStream (const ValueUnion&, OutputStream& output) const { jassertfalse; // Can't write an object to a stream! output.writeCompressedInt (0); } }; class var::VariantType_Array : public var::VariantType { public: VariantType_Array() noexcept {} static const VariantType_Array instance; void cleanUp (ValueUnion& data) const noexcept { delete data.arrayValue; } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.arrayValue = new Array (*(source.arrayValue)); } String toString (const ValueUnion&) const { return "[Array]"; } bool isArray() const noexcept { return true; } Array* toArray (const ValueUnion& data) const noexcept { return data.arrayValue; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { const Array* const otherArray = otherType.toArray (otherData); return otherArray != nullptr && *otherArray == *(data.arrayValue); } void writeToStream (const ValueUnion& data, OutputStream& output) const { MemoryOutputStream buffer (512); const int numItems = data.arrayValue->size(); buffer.writeCompressedInt (numItems); for (int i = 0; i < numItems; ++i) data.arrayValue->getReference(i).writeToStream (buffer); output.writeCompressedInt (1 + buffer.getDataSize()); output.writeByte (varMarker_Array); output << buffer; } }; class var::VariantType_Method : public var::VariantType { public: VariantType_Method() noexcept {} static const VariantType_Method instance; String toString (const ValueUnion&) const { return "Method"; } bool toBool (const ValueUnion& data) const noexcept { return data.methodValue != 0; } bool isMethod() const noexcept { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept { return otherType.isMethod() && otherData.methodValue == data.methodValue; } void writeToStream (const ValueUnion&, OutputStream& output) const { jassertfalse; // Can't write a method to a stream! output.writeCompressedInt (0); } }; const var::VariantType_Void var::VariantType_Void::instance; const var::VariantType_Int var::VariantType_Int::instance; const var::VariantType_Int64 var::VariantType_Int64::instance; const var::VariantType_Bool var::VariantType_Bool::instance; const var::VariantType_Double var::VariantType_Double::instance; const var::VariantType_String var::VariantType_String::instance; const var::VariantType_Object var::VariantType_Object::instance; const var::VariantType_Array var::VariantType_Array::instance; const var::VariantType_Method var::VariantType_Method::instance; var::var() noexcept : type (&VariantType_Void::instance) { } var::~var() noexcept { type->cleanUp (value); } const var var::null; var::var (const var& valueToCopy) : type (valueToCopy.type) { type->createCopy (value, valueToCopy.value); } var::var (const int value_) noexcept : type (&VariantType_Int::instance) { value.intValue = value_; } var::var (const int64 value_) noexcept : type (&VariantType_Int64::instance) { value.int64Value = value_; } var::var (const bool value_) noexcept : type (&VariantType_Bool::instance) { value.boolValue = value_; } var::var (const double value_) noexcept : type (&VariantType_Double::instance) { value.doubleValue = value_; } var::var (const String& value_) : type (&VariantType_String::instance) { new (value.stringValue) String (value_); } var::var (const char* const value_) : type (&VariantType_String::instance) { new (value.stringValue) String (value_); } var::var (const wchar_t* const value_) : type (&VariantType_String::instance) { new (value.stringValue) String (value_); } var::var (const Array& value_) : type (&VariantType_Array::instance) { value.arrayValue = new Array (value_); } var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) { value.objectValue = object; if (object != nullptr) object->incReferenceCount(); } var::var (MethodFunction method_) noexcept : type (&VariantType_Method::instance) { value.methodValue = method_; } bool var::isVoid() const noexcept { return type->isVoid(); } bool var::isInt() const noexcept { return type->isInt(); } bool var::isInt64() const noexcept { return type->isInt64(); } bool var::isBool() const noexcept { return type->isBool(); } bool var::isDouble() const noexcept { return type->isDouble(); } bool var::isString() const noexcept { return type->isString(); } bool var::isObject() const noexcept { return type->isObject(); } bool var::isArray() const noexcept { return type->isArray(); } bool var::isMethod() const noexcept { return type->isMethod(); } var::operator int() const noexcept { return type->toInt (value); } var::operator int64() const noexcept { return type->toInt64 (value); } var::operator bool() const noexcept { return type->toBool (value); } var::operator float() const noexcept { return (float) type->toDouble (value); } var::operator double() const noexcept { return type->toDouble (value); } String var::toString() const { return type->toString (value); } var::operator String() const { return type->toString (value); } ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } Array* var::getArray() const noexcept { return type->toArray (value); } DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast (getObject()); } void var::swapWith (var& other) noexcept { std::swap (type, other.type); std::swap (value, other.value); } const var& var::operator= (const var& newValue) { type->cleanUp (value); type = newValue.type; type->createCopy (value, newValue.value); return *this; } const var& var::operator= (const int newValue) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = newValue; return *this; } const var& var::operator= (const int64 newValue) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = newValue; return *this; } const var& var::operator= (const bool newValue) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = newValue; return *this; } const var& var::operator= (const double newValue) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = newValue; return *this; } const var& var::operator= (const char* const newValue) { var v (newValue); swapWith (v); return *this; } const var& var::operator= (const wchar_t* const newValue) { var v (newValue); swapWith (v); return *this; } const var& var::operator= (const String& newValue) { var v (newValue); swapWith (v); return *this; } const var& var::operator= (const Array& newValue) { var v (newValue); swapWith (v); return *this; } const var& var::operator= (ReferenceCountedObject* newValue) { var v (newValue); swapWith (v); return *this; } const var& var::operator= (MethodFunction newValue) { var v (newValue); swapWith (v); return *this; } bool var::equals (const var& other) const noexcept { return type->equals (value, other.value, *other.type); } bool var::equalsWithSameType (const var& other) const noexcept { return type == other.type && equals (other); } bool operator== (const var& v1, const var& v2) noexcept { return v1.equals (v2); } bool operator!= (const var& v1, const var& v2) noexcept { return ! v1.equals (v2); } bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; } bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; } bool operator== (const var& v1, const char* const v2) { return v1.toString() == v2; } bool operator!= (const var& v1, const char* const v2) { return v1.toString() != v2; } var var::operator[] (const Identifier& propertyName) const { DynamicObject* const o = getDynamicObject(); return o != nullptr ? o->getProperty (propertyName) : var::null; } var var::invoke (const Identifier& method, const var* arguments, int numArguments) const { DynamicObject* const o = getDynamicObject(); return o != nullptr ? o->invokeMethod (method, arguments, numArguments) : var::null; } var var::invokeMethod (DynamicObject* const target, const var* const arguments, const int numArguments) const { jassert (target != nullptr); if (isMethod()) return (target->*(value.methodValue)) (arguments, numArguments); return var::null; } var var::call (const Identifier& method) const { return invoke (method, nullptr, 0); } var var::call (const Identifier& method, const var& arg1) const { return invoke (method, &arg1, 1); } var var::call (const Identifier& method, const var& arg1, const var& arg2) const { var args[] = { arg1, arg2 }; return invoke (method, args, 2); } var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3) { var args[] = { arg1, arg2, arg3 }; return invoke (method, args, 3); } var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const { var args[] = { arg1, arg2, arg3, arg4 }; return invoke (method, args, 4); } var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const { var args[] = { arg1, arg2, arg3, arg4, arg5 }; return invoke (method, args, 5); } int var::size() const { const Array* const array = getArray(); return array != nullptr ? array->size() : 0; } const var& var::operator[] (int arrayIndex) const { const Array* const array = getArray(); // When using this method, the var must actually be an array, and the index // must be in-range! jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); return array->getReference (arrayIndex); } var& var::operator[] (int arrayIndex) { const Array* const array = getArray(); // When using this method, the var must actually be an array, and the index // must be in-range! jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); return array->getReference (arrayIndex); } Array* var::convertToArray() { Array* array = getArray(); if (array == nullptr) { const Array tempVar; var v (tempVar); array = v.value.arrayValue; if (! isVoid()) array->add (*this); swapWith (v); } return array; } void var::append (const var& n) { convertToArray()->add (n); } void var::remove (const int index) { Array* const array = getArray(); if (array != nullptr) array->remove (index); } void var::insert (const int index, const var& n) { convertToArray()->insert (index, n); } void var::resize (const int numArrayElementsWanted) { convertToArray()->resize (numArrayElementsWanted); } int var::indexOf (const var& n) const { const Array* const array = getArray(); return array != nullptr ? array->indexOf (n) : -1; } void var::writeToStream (OutputStream& output) const { type->writeToStream (value, output); } var var::readFromStream (InputStream& input) { const int numBytes = input.readCompressedInt(); if (numBytes > 0) { switch (input.readByte()) { case varMarker_Int: return var (input.readInt()); case varMarker_Int64: return var (input.readInt64()); case varMarker_BoolTrue: return var (true); case varMarker_BoolFalse: return var (false); case varMarker_Double: return var (input.readDouble()); case varMarker_String: { MemoryOutputStream mo; mo.writeFromInputStream (input, numBytes - 1); return var (mo.toUTF8()); } case varMarker_Array: { var v; Array* const destArray = v.convertToArray(); for (int i = input.readCompressedInt(); --i >= 0;) destArray->add (readFromStream (input)); return v; } default: input.skipNextBytes (numBytes - 1); break; } } return var::null; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Variant.cpp ***/ /*** Start of inlined file: juce_NamedValueSet.cpp ***/ BEGIN_JUCE_NAMESPACE NamedValueSet::NamedValue::NamedValue() noexcept { } inline NamedValueSet::NamedValue::NamedValue (const Identifier& name_, const var& value_) : name (name_), value (value_) { } NamedValueSet::NamedValue::NamedValue (const NamedValue& other) : name (other.name), value (other.value) { } NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (const NamedValueSet::NamedValue& other) { name = other.name; value = other.value; return *this; } bool NamedValueSet::NamedValue::operator== (const NamedValueSet::NamedValue& other) const noexcept { return name == other.name && value == other.value; } NamedValueSet::NamedValueSet() noexcept { } NamedValueSet::NamedValueSet (const NamedValueSet& other) { values.addCopyOfList (other.values); } NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) { clear(); values.addCopyOfList (other.values); return *this; } NamedValueSet::~NamedValueSet() { clear(); } void NamedValueSet::clear() { values.deleteAll(); } bool NamedValueSet::operator== (const NamedValueSet& other) const { const NamedValue* i1 = values; const NamedValue* i2 = other.values; while (i1 != nullptr && i2 != nullptr) { if (! (*i1 == *i2)) return false; i1 = i1->nextListItem; i2 = i2->nextListItem; } return true; } bool NamedValueSet::operator!= (const NamedValueSet& other) const { return ! operator== (other); } int NamedValueSet::size() const noexcept { return values.size(); } const var& NamedValueSet::operator[] (const Identifier& name) const { for (NamedValue* i = values; i != nullptr; i = i->nextListItem) if (i->name == name) return i->value; return var::null; } var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const { const var* const v = getVarPointer (name); return v != nullptr ? *v : defaultReturnValue; } var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept { for (NamedValue* i = values; i != nullptr; i = i->nextListItem) if (i->name == name) return &(i->value); return nullptr; } bool NamedValueSet::set (const Identifier& name, const var& newValue) { LinkedListPointer* i = &values; while (i->get() != nullptr) { NamedValue* const v = i->get(); if (v->name == name) { if (v->value.equalsWithSameType (newValue)) return false; v->value = newValue; return true; } i = &(v->nextListItem); } i->insertNext (new NamedValue (name, newValue)); return true; } bool NamedValueSet::contains (const Identifier& name) const { return getVarPointer (name) != nullptr; } bool NamedValueSet::remove (const Identifier& name) { LinkedListPointer* i = &values; for (;;) { NamedValue* const v = i->get(); if (v == nullptr) break; if (v->name == name) { delete i->removeNext(); return true; } i = &(v->nextListItem); } return false; } const Identifier NamedValueSet::getName (const int index) const { const NamedValue* const v = values[index]; jassert (v != nullptr); return v->name; } const var& NamedValueSet::getValueAt (const int index) const { const NamedValue* const v = values[index]; jassert (v != nullptr); return v->value; } void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) { clear(); LinkedListPointer::Appender appender (values); const int numAtts = xml.getNumAttributes(); // xxx inefficient - should write an att iterator.. for (int i = 0; i < numAtts; ++i) appender.append (new NamedValue (xml.getAttributeName (i), var (xml.getAttributeValue (i)))); } void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const { for (NamedValue* i = values; i != nullptr; i = i->nextListItem) { jassert (! i->value.isObject()); // DynamicObjects can't be stored as XML! xml.setAttribute (i->name.toString(), i->value.toString()); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_NamedValueSet.cpp ***/ /*** Start of inlined file: juce_DynamicObject.cpp ***/ BEGIN_JUCE_NAMESPACE DynamicObject::DynamicObject() { } DynamicObject::~DynamicObject() { } bool DynamicObject::hasProperty (const Identifier& propertyName) const { const var* const v = properties.getVarPointer (propertyName); return v != nullptr && ! v->isMethod(); } const var DynamicObject::getProperty (const Identifier& propertyName) const { return properties [propertyName]; } void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue) { properties.set (propertyName, newValue); } void DynamicObject::removeProperty (const Identifier& propertyName) { properties.remove (propertyName); } bool DynamicObject::hasMethod (const Identifier& methodName) const { return getProperty (methodName).isMethod(); } const var DynamicObject::invokeMethod (const Identifier& methodName, const var* parameters, int numParameters) { return properties [methodName].invokeMethod (this, parameters, numParameters); } void DynamicObject::setMethod (const Identifier& name, var::MethodFunction methodFunction) { properties.set (name, var (methodFunction)); } void DynamicObject::clear() { properties.clear(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_DynamicObject.cpp ***/ /*** Start of inlined file: juce_Expression.cpp ***/ BEGIN_JUCE_NAMESPACE class Expression::Term : public SingleThreadedReferenceCountedObject { public: Term() {} virtual ~Term() {} virtual Type getType() const noexcept = 0; virtual Term* clone() const = 0; virtual const ReferenceCountedObjectPtr resolve (const Scope&, int recursionDepth) = 0; virtual String toString() const = 0; virtual double toDouble() const { return 0; } virtual int getInputIndexFor (const Term*) const { return -1; } virtual int getOperatorPrecedence() const { return 0; } virtual int getNumInputs() const { return 0; } virtual Term* getInput (int) const { return nullptr; } virtual const ReferenceCountedObjectPtr negated(); virtual const ReferenceCountedObjectPtr createTermToEvaluateInput (const Scope&, const Term* /*inputTerm*/, double /*overallTarget*/, Term* /*topLevelTerm*/) const { jassertfalse; return nullptr; } virtual String getName() const { jassertfalse; // You shouldn't call this for an expression that's not actually a function! return String::empty; } virtual void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) { for (int i = getNumInputs(); --i >= 0;) getInput (i)->renameSymbol (oldSymbol, newName, scope, recursionDepth); } class SymbolVisitor { public: virtual ~SymbolVisitor() {} virtual void useSymbol (const Symbol&) = 0; }; virtual void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) { for (int i = getNumInputs(); --i >= 0;) getInput(i)->visitAllSymbols (visitor, scope, recursionDepth); } private: JUCE_DECLARE_NON_COPYABLE (Term); }; class Expression::Helpers { public: typedef ReferenceCountedObjectPtr TermPtr; // This helper function is needed to work around VC6 scoping bugs static inline const TermPtr& getTermFor (const Expression& exp) noexcept { return exp.term; } static void checkRecursionDepth (const int depth) { if (depth > 256) throw EvaluationError ("Recursive symbol references"); } friend class Expression::Term; // (also only needed as a VC6 workaround) /** An exception that can be thrown by Expression::evaluate(). */ class EvaluationError : public std::exception { public: EvaluationError (const String& description_) : description (description_) { DBG ("Expression::EvaluationError: " + description); } String description; }; class Constant : public Term { public: Constant (const double value_, const bool isResolutionTarget_) : value (value_), isResolutionTarget (isResolutionTarget_) {} Type getType() const noexcept { return constantType; } Term* clone() const { return new Constant (value, isResolutionTarget); } const TermPtr resolve (const Scope&, int) { return this; } double toDouble() const { return value; } const TermPtr negated() { return new Constant (-value, isResolutionTarget); } String toString() const { String s (value); if (isResolutionTarget) s = "@" + s; return s; } double value; bool isResolutionTarget; }; class BinaryTerm : public Term { public: BinaryTerm (Term* const left_, Term* const right_) : left (left_), right (right_) { jassert (left_ != nullptr && right_ != nullptr); } int getInputIndexFor (const Term* possibleInput) const { return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1); } Type getType() const noexcept { return operatorType; } int getNumInputs() const { return 2; } Term* getInput (int index) const { return index == 0 ? left.getObject() : (index == 1 ? right.getObject() : 0); } virtual double performFunction (double left, double right) const = 0; virtual void writeOperator (String& dest) const = 0; const TermPtr resolve (const Scope& scope, int recursionDepth) { return new Constant (performFunction (left->resolve (scope, recursionDepth)->toDouble(), right->resolve (scope, recursionDepth)->toDouble()), false); } String toString() const { String s; const int ourPrecendence = getOperatorPrecedence(); if (left->getOperatorPrecedence() > ourPrecendence) s << '(' << left->toString() << ')'; else s = left->toString(); writeOperator (s); if (right->getOperatorPrecedence() >= ourPrecendence) s << '(' << right->toString() << ')'; else s << right->toString(); return s; } protected: const TermPtr left, right; const TermPtr createDestinationTerm (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const { jassert (input == left || input == right); if (input != left && input != right) return nullptr; const Term* const dest = findDestinationFor (topLevelTerm, this); if (dest == nullptr) return new Constant (overallTarget, false); return dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm); } }; class SymbolTerm : public Term { public: explicit SymbolTerm (const String& symbol_) : symbol (symbol_) {} const TermPtr resolve (const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); return getTermFor (scope.getSymbolValue (symbol))->resolve (scope, recursionDepth + 1); } Type getType() const noexcept { return symbolType; } Term* clone() const { return new SymbolTerm (symbol); } String toString() const { return symbol; } String getName() const { return symbol; } void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); visitor.useSymbol (Symbol (scope.getScopeUID(), symbol)); getTermFor (scope.getSymbolValue (symbol))->visitAllSymbols (visitor, scope, recursionDepth + 1); } void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int /*recursionDepth*/) { if (oldSymbol.symbolName == symbol && scope.getScopeUID() == oldSymbol.scopeUID) symbol = newName; } String symbol; }; class Function : public Term { public: explicit Function (const String& functionName_) : functionName (functionName_) {} Function (const String& functionName_, const Array& parameters_) : functionName (functionName_), parameters (parameters_) {} Type getType() const noexcept { return functionType; } Term* clone() const { return new Function (functionName, parameters); } int getNumInputs() const { return parameters.size(); } Term* getInput (int i) const { return getTermFor (parameters [i]); } String getName() const { return functionName; } const TermPtr resolve (const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); double result = 0; const int numParams = parameters.size(); if (numParams > 0) { HeapBlock params (numParams); for (int i = 0; i < numParams; ++i) params[i] = getTermFor (parameters.getReference(i))->resolve (scope, recursionDepth + 1)->toDouble(); result = scope.evaluateFunction (functionName, params, numParams); } else { result = scope.evaluateFunction (functionName, nullptr, 0); } return new Constant (result, false); } int getInputIndexFor (const Term* possibleInput) const { for (int i = 0; i < parameters.size(); ++i) if (getTermFor (parameters.getReference(i)) == possibleInput) return i; return -1; } String toString() const { if (parameters.size() == 0) return functionName + "()"; String s (functionName + " ("); for (int i = 0; i < parameters.size(); ++i) { s << getTermFor (parameters.getReference(i))->toString(); if (i < parameters.size() - 1) s << ", "; } s << ')'; return s; } const String functionName; Array parameters; }; class DotOperator : public BinaryTerm { public: DotOperator (SymbolTerm* const left_, Term* const right_) : BinaryTerm (left_, right_) {} const TermPtr resolve (const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); EvaluationVisitor visitor (right, recursionDepth + 1); scope.visitRelativeScope (getSymbol()->symbol, visitor); return visitor.output; } Term* clone() const { return new DotOperator (getSymbol(), right); } String getName() const { return "."; } int getOperatorPrecedence() const { return 1; } void writeOperator (String& dest) const { dest << '.'; } double performFunction (double, double) const { return 0.0; } void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); visitor.useSymbol (Symbol (scope.getScopeUID(), getSymbol()->symbol)); SymbolVisitingVisitor v (right, visitor, recursionDepth + 1); try { scope.visitRelativeScope (getSymbol()->symbol, v); } catch (...) {} } void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) { checkRecursionDepth (recursionDepth); getSymbol()->renameSymbol (oldSymbol, newName, scope, recursionDepth); SymbolRenamingVisitor visitor (right, oldSymbol, newName, recursionDepth + 1); try { scope.visitRelativeScope (getSymbol()->symbol, visitor); } catch (...) {} } private: class EvaluationVisitor : public Scope::Visitor { public: EvaluationVisitor (const TermPtr& input_, const int recursionCount_) : input (input_), output (input_), recursionCount (recursionCount_) {} void visit (const Scope& scope) { output = input->resolve (scope, recursionCount); } const TermPtr input; TermPtr output; const int recursionCount; private: JUCE_DECLARE_NON_COPYABLE (EvaluationVisitor); }; class SymbolVisitingVisitor : public Scope::Visitor { public: SymbolVisitingVisitor (const TermPtr& input_, SymbolVisitor& visitor_, const int recursionCount_) : input (input_), visitor (visitor_), recursionCount (recursionCount_) {} void visit (const Scope& scope) { input->visitAllSymbols (visitor, scope, recursionCount); } private: const TermPtr input; SymbolVisitor& visitor; const int recursionCount; JUCE_DECLARE_NON_COPYABLE (SymbolVisitingVisitor); }; class SymbolRenamingVisitor : public Scope::Visitor { public: SymbolRenamingVisitor (const TermPtr& input_, const Expression::Symbol& symbol_, const String& newName_, const int recursionCount_) : input (input_), symbol (symbol_), newName (newName_), recursionCount (recursionCount_) {} void visit (const Scope& scope) { input->renameSymbol (symbol, newName, scope, recursionCount); } private: const TermPtr input; const Symbol& symbol; const String newName; const int recursionCount; JUCE_DECLARE_NON_COPYABLE (SymbolRenamingVisitor); }; SymbolTerm* getSymbol() const { return static_cast (left.getObject()); } JUCE_DECLARE_NON_COPYABLE (DotOperator); }; class Negate : public Term { public: explicit Negate (const TermPtr& input_) : input (input_) { jassert (input_ != nullptr); } Type getType() const noexcept { return operatorType; } int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; } int getNumInputs() const { return 1; } Term* getInput (int index) const { return index == 0 ? input.getObject() : nullptr; } Term* clone() const { return new Negate (input->clone()); } const TermPtr resolve (const Scope& scope, int recursionDepth) { return new Constant (-input->resolve (scope, recursionDepth)->toDouble(), false); } String getName() const { return "-"; } const TermPtr negated() { return input; } const TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input_, double overallTarget, Term* topLevelTerm) const { (void) input_; jassert (input_ == input); const Term* const dest = findDestinationFor (topLevelTerm, this); return new Negate (dest == nullptr ? new Constant (overallTarget, false) : dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm)); } String toString() const { if (input->getOperatorPrecedence() > 0) return "-(" + input->toString() + ")"; else return "-" + input->toString(); } private: const TermPtr input; }; class Add : public BinaryTerm { public: Add (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Add (left->clone(), right->clone()); } double performFunction (double lhs, double rhs) const { return lhs + rhs; } int getOperatorPrecedence() const { return 3; } String getName() const { return "+"; } void writeOperator (String& dest) const { dest << " + "; } const TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); if (newDest == nullptr) return nullptr; return new Subtract (newDest, (input == left ? right : left)->clone()); } private: JUCE_DECLARE_NON_COPYABLE (Add); }; class Subtract : public BinaryTerm { public: Subtract (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Subtract (left->clone(), right->clone()); } double performFunction (double lhs, double rhs) const { return lhs - rhs; } int getOperatorPrecedence() const { return 3; } String getName() const { return "-"; } void writeOperator (String& dest) const { dest << " - "; } const TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); if (newDest == nullptr) return nullptr; if (input == left) return new Add (newDest, right->clone()); else return new Subtract (left->clone(), newDest); } private: JUCE_DECLARE_NON_COPYABLE (Subtract); }; class Multiply : public BinaryTerm { public: Multiply (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Multiply (left->clone(), right->clone()); } double performFunction (double lhs, double rhs) const { return lhs * rhs; } String getName() const { return "*"; } void writeOperator (String& dest) const { dest << " * "; } int getOperatorPrecedence() const { return 2; } const TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); if (newDest == nullptr) return nullptr; return new Divide (newDest, (input == left ? right : left)->clone()); } private: JUCE_DECLARE_NON_COPYABLE (Multiply); }; class Divide : public BinaryTerm { public: Divide (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {} Term* clone() const { return new Divide (left->clone(), right->clone()); } double performFunction (double lhs, double rhs) const { return lhs / rhs; } String getName() const { return "/"; } void writeOperator (String& dest) const { dest << " / "; } int getOperatorPrecedence() const { return 2; } const TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); if (newDest == nullptr) return nullptr; if (input == left) return new Multiply (newDest, right->clone()); else return new Divide (left->clone(), newDest); } private: JUCE_DECLARE_NON_COPYABLE (Divide); }; static Term* findDestinationFor (Term* const topLevel, const Term* const inputTerm) { const int inputIndex = topLevel->getInputIndexFor (inputTerm); if (inputIndex >= 0) return topLevel; for (int i = topLevel->getNumInputs(); --i >= 0;) { Term* const t = findDestinationFor (topLevel->getInput (i), inputTerm); if (t != nullptr) return t; } return nullptr; } static Constant* findTermToAdjust (Term* const term, const bool mustBeFlagged) { { Constant* const c = dynamic_cast (term); if (c != nullptr && (c->isResolutionTarget || ! mustBeFlagged)) return c; } if (dynamic_cast (term) != nullptr) return nullptr; int i; const int numIns = term->getNumInputs(); for (i = 0; i < numIns; ++i) { Constant* const c = dynamic_cast (term->getInput (i)); if (c != nullptr && (c->isResolutionTarget || ! mustBeFlagged)) return c; } for (i = 0; i < numIns; ++i) { Constant* const c = findTermToAdjust (term->getInput (i), mustBeFlagged); if (c != nullptr) return c; } return nullptr; } static bool containsAnySymbols (const Term* const t) { if (t->getType() == Expression::symbolType) return true; for (int i = t->getNumInputs(); --i >= 0;) if (containsAnySymbols (t->getInput (i))) return true; return false; } class SymbolCheckVisitor : public Term::SymbolVisitor { public: SymbolCheckVisitor (const Symbol& symbol_) : wasFound (false), symbol (symbol_) {} void useSymbol (const Symbol& s) { wasFound = wasFound || s == symbol; } bool wasFound; private: const Symbol& symbol; JUCE_DECLARE_NON_COPYABLE (SymbolCheckVisitor); }; class SymbolListVisitor : public Term::SymbolVisitor { public: SymbolListVisitor (Array& list_) : list (list_) {} void useSymbol (const Symbol& s) { list.addIfNotAlreadyThere (s); } private: Array& list; JUCE_DECLARE_NON_COPYABLE (SymbolListVisitor); }; class Parser { public: Parser (String::CharPointerType& stringToParse) : text (stringToParse) { } const TermPtr readUpToComma() { if (text.isEmpty()) return new Constant (0.0, false); const TermPtr e (readExpression()); if (e == nullptr || ((! readOperator (",")) && ! text.isEmpty())) throw ParseError ("Syntax error: \"" + String (text) + "\""); return e; } private: String::CharPointerType& text; static inline bool isDecimalDigit (const juce_wchar c) noexcept { return c >= '0' && c <= '9'; } bool readChar (const juce_wchar required) noexcept { if (*text == required) { ++text; return true; } return false; } bool readOperator (const char* ops, char* const opType = nullptr) noexcept { text = text.findEndOfWhitespace(); while (*ops != 0) { if (readChar (*ops)) { if (opType != nullptr) *opType = *ops; return true; } ++ops; } return false; } bool readIdentifier (String& identifier) noexcept { text = text.findEndOfWhitespace(); String::CharPointerType t (text); int numChars = 0; if (t.isLetter() || *t == '_') { ++t; ++numChars; while (t.isLetterOrDigit() || *t == '_') { ++t; ++numChars; } } if (numChars > 0) { identifier = String (text, numChars); text = t; return true; } return false; } Term* readNumber() noexcept { text = text.findEndOfWhitespace(); String::CharPointerType t (text); const bool isResolutionTarget = (*t == '@'); if (isResolutionTarget) { ++t; t = t.findEndOfWhitespace(); text = t; } if (*t == '-') { ++t; t = t.findEndOfWhitespace(); } if (isDecimalDigit (*t) || (*t == '.' && isDecimalDigit (t[1]))) return new Constant (CharacterFunctions::readDoubleValue (text), isResolutionTarget); return nullptr; } const TermPtr readExpression() { TermPtr lhs (readMultiplyOrDivideExpression()); char opType; while (lhs != nullptr && readOperator ("+-", &opType)) { TermPtr rhs (readMultiplyOrDivideExpression()); if (rhs == nullptr) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '+') lhs = new Add (lhs, rhs); else lhs = new Subtract (lhs, rhs); } return lhs; } const TermPtr readMultiplyOrDivideExpression() { TermPtr lhs (readUnaryExpression()); char opType; while (lhs != nullptr && readOperator ("*/", &opType)) { TermPtr rhs (readUnaryExpression()); if (rhs == nullptr) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '*') lhs = new Multiply (lhs, rhs); else lhs = new Divide (lhs, rhs); } return lhs; } const TermPtr readUnaryExpression() { char opType; if (readOperator ("+-", &opType)) { TermPtr term (readUnaryExpression()); if (term == nullptr) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '-') term = term->negated(); return term; } return readPrimaryExpression(); } const TermPtr readPrimaryExpression() { TermPtr e (readParenthesisedExpression()); if (e != nullptr) return e; e = readNumber(); if (e != nullptr) return e; return readSymbolOrFunction(); } const TermPtr readSymbolOrFunction() { String identifier; if (readIdentifier (identifier)) { if (readOperator ("(")) // method call... { Function* const f = new Function (identifier); ScopedPointer func (f); // (can't use ScopedPointer in MSVC) TermPtr param (readExpression()); if (param == nullptr) { if (readOperator (")")) return func.release(); throw ParseError ("Expected parameters after \"" + identifier + " (\""); } f->parameters.add (Expression (param)); while (readOperator (",")) { param = readExpression(); if (param == nullptr) throw ParseError ("Expected expression after \",\""); f->parameters.add (Expression (param)); } if (readOperator (")")) return func.release(); throw ParseError ("Expected \")\""); } else if (readOperator (".")) { TermPtr rhs (readSymbolOrFunction()); if (rhs == nullptr) throw ParseError ("Expected symbol or function after \".\""); if (identifier == "this") return rhs; return new DotOperator (new SymbolTerm (identifier), rhs); } else // just a symbol.. { jassert (identifier.trim() == identifier); return new SymbolTerm (identifier); } } return nullptr; } const TermPtr readParenthesisedExpression() { if (! readOperator ("(")) return nullptr; const TermPtr e (readExpression()); if (e == nullptr || ! readOperator (")")) return nullptr; return e; } JUCE_DECLARE_NON_COPYABLE (Parser); }; }; Expression::Expression() : term (new Expression::Helpers::Constant (0, false)) { } Expression::~Expression() { } Expression::Expression (Term* const term_) : term (term_) { jassert (term != nullptr); } Expression::Expression (const double constant) : term (new Expression::Helpers::Constant (constant, false)) { } Expression::Expression (const Expression& other) : term (other.term) { } Expression& Expression::operator= (const Expression& other) { term = other.term; return *this; } Expression::Expression (const String& stringToParse) { String::CharPointerType text (stringToParse.getCharPointer()); Helpers::Parser parser (text); term = parser.readUpToComma(); } Expression Expression::parse (String::CharPointerType& stringToParse) { Helpers::Parser parser (stringToParse); return Expression (parser.readUpToComma()); } double Expression::evaluate() const { return evaluate (Expression::Scope()); } double Expression::evaluate (const Expression::Scope& scope) const { try { return term->resolve (scope, 0)->toDouble(); } catch (Helpers::EvaluationError&) {} return 0; } double Expression::evaluate (const Scope& scope, String& evaluationError) const { try { return term->resolve (scope, 0)->toDouble(); } catch (Helpers::EvaluationError& e) { evaluationError = e.description; } return 0; } Expression Expression::operator+ (const Expression& other) const { return Expression (new Helpers::Add (term, other.term)); } Expression Expression::operator- (const Expression& other) const { return Expression (new Helpers::Subtract (term, other.term)); } Expression Expression::operator* (const Expression& other) const { return Expression (new Helpers::Multiply (term, other.term)); } Expression Expression::operator/ (const Expression& other) const { return Expression (new Helpers::Divide (term, other.term)); } Expression Expression::operator-() const { return Expression (term->negated()); } Expression Expression::symbol (const String& symbol) { return Expression (new Helpers::SymbolTerm (symbol)); } Expression Expression::function (const String& functionName, const Array& parameters) { return Expression (new Helpers::Function (functionName, parameters)); } Expression Expression::adjustedToGiveNewResult (const double targetValue, const Expression::Scope& scope) const { ScopedPointer newTerm (term->clone()); Helpers::Constant* termToAdjust = Helpers::findTermToAdjust (newTerm, true); if (termToAdjust == nullptr) termToAdjust = Helpers::findTermToAdjust (newTerm, false); if (termToAdjust == nullptr) { newTerm = new Helpers::Add (newTerm.release(), new Helpers::Constant (0, false)); termToAdjust = Helpers::findTermToAdjust (newTerm, false); } jassert (termToAdjust != nullptr); const Term* const parent = Helpers::findDestinationFor (newTerm, termToAdjust); if (parent == nullptr) { termToAdjust->value = targetValue; } else { const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (scope, termToAdjust, targetValue, newTerm)); if (reverseTerm == nullptr) return Expression (targetValue); termToAdjust->value = reverseTerm->resolve (scope, 0)->toDouble(); } return Expression (newTerm.release()); } Expression Expression::withRenamedSymbol (const Expression::Symbol& oldSymbol, const String& newName, const Scope& scope) const { jassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); if (oldSymbol.symbolName == newName) return *this; Expression e (term->clone()); e.term->renameSymbol (oldSymbol, newName, scope, 0); return e; } bool Expression::referencesSymbol (const Expression::Symbol& symbolToCheck, const Scope& scope) const { Helpers::SymbolCheckVisitor visitor (symbolToCheck); try { term->visitAllSymbols (visitor, scope, 0); } catch (Helpers::EvaluationError&) {} return visitor.wasFound; } void Expression::findReferencedSymbols (Array& results, const Scope& scope) const { try { Helpers::SymbolListVisitor visitor (results); term->visitAllSymbols (visitor, scope, 0); } catch (Helpers::EvaluationError&) {} } String Expression::toString() const { return term->toString(); } bool Expression::usesAnySymbols() const { return Helpers::containsAnySymbols (term); } Expression::Type Expression::getType() const noexcept { return term->getType(); } String Expression::getSymbolOrFunction() const { return term->getName(); } int Expression::getNumInputs() const { return term->getNumInputs(); } Expression Expression::getInput (int index) const { return Expression (term->getInput (index)); } const ReferenceCountedObjectPtr Expression::Term::negated() { return new Helpers::Negate (this); } Expression::ParseError::ParseError (const String& message) : description (message) { DBG ("Expression::ParseError: " + message); } Expression::Symbol::Symbol (const String& scopeUID_, const String& symbolName_) : scopeUID (scopeUID_), symbolName (symbolName_) { } bool Expression::Symbol::operator== (const Symbol& other) const noexcept { return symbolName == other.symbolName && scopeUID == other.scopeUID; } bool Expression::Symbol::operator!= (const Symbol& other) const noexcept { return ! operator== (other); } Expression::Scope::Scope() {} Expression::Scope::~Scope() {} const Expression Expression::Scope::getSymbolValue (const String& symbol) const { throw Helpers::EvaluationError ("Unknown symbol: " + symbol); } double Expression::Scope::evaluateFunction (const String& functionName, const double* parameters, int numParams) const { if (numParams > 0) { if (functionName == "min") { double v = parameters[0]; for (int i = 1; i < numParams; ++i) v = jmin (v, parameters[i]); return v; } else if (functionName == "max") { double v = parameters[0]; for (int i = 1; i < numParams; ++i) v = jmax (v, parameters[i]); return v; } else if (numParams == 1) { if (functionName == "sin") return sin (parameters[0]); else if (functionName == "cos") return cos (parameters[0]); else if (functionName == "tan") return tan (parameters[0]); else if (functionName == "abs") return std::abs (parameters[0]); } } throw Helpers::EvaluationError ("Unknown function: \"" + functionName + "\""); } void Expression::Scope::visitRelativeScope (const String& scopeName, Visitor&) const { throw Helpers::EvaluationError ("Unknown symbol: " + scopeName); } const String Expression::Scope::getScopeUID() const { return String::empty; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Expression.cpp ***/ /*** Start of inlined file: juce_BlowFish.cpp ***/ BEGIN_JUCE_NAMESPACE BlowFish::BlowFish (const void* const keyData, const int keyBytes) { jassert (keyData != nullptr); jassert (keyBytes > 0); static const uint32 initialPValues [18] = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b }; static const uint32 initialSValues [4 * 256] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 }; memcpy (p, initialPValues, sizeof (p)); int i, j = 0; for (i = 4; --i >= 0;) { s[i].malloc (256); memcpy (s[i], initialSValues + i * 256, 256 * sizeof (uint32)); } for (i = 0; i < 18; ++i) { uint32 d = 0; for (int k = 0; k < 4; ++k) { d = (d << 8) | static_cast (keyData)[j]; if (++j >= keyBytes) j = 0; } p[i] = initialPValues[i] ^ d; } uint32 l = 0, r = 0; for (i = 0; i < 18; i += 2) { encrypt (l, r); p[i] = l; p[i + 1] = r; } for (i = 0; i < 4; ++i) { for (j = 0; j < 256; j += 2) { encrypt (l, r); s[i][j] = l; s[i][j + 1] = r; } } } BlowFish::BlowFish (const BlowFish& other) { for (int i = 4; --i >= 0;) s[i].malloc (256); operator= (other); } BlowFish& BlowFish::operator= (const BlowFish& other) { memcpy (p, other.p, sizeof (p)); for (int i = 4; --i >= 0;) memcpy (s[i], other.s[i], 256 * sizeof (uint32)); return *this; } BlowFish::~BlowFish() { } uint32 BlowFish::F (const uint32 x) const noexcept { return ((s[0][(x >> 24) & 0xff] + s[1][(x >> 16) & 0xff]) ^ s[2][(x >> 8) & 0xff]) + s[3][x & 0xff]; } void BlowFish::encrypt (uint32& data1, uint32& data2) const noexcept { uint32 l = data1; uint32 r = data2; for (int i = 0; i < 16; ++i) { l ^= p[i]; r ^= F(l); std::swap (l, r); } data1 = r ^ p[17]; data2 = l ^ p[16]; } void BlowFish::decrypt (uint32& data1, uint32& data2) const noexcept { uint32 l = data1; uint32 r = data2; for (int i = 17; i > 1; --i) { l ^= p[i]; r ^= F(l); std::swap (l, r); } data1 = r ^ p[0]; data2 = l ^ p[1]; } END_JUCE_NAMESPACE /*** End of inlined file: juce_BlowFish.cpp ***/ /*** Start of inlined file: juce_MD5.cpp ***/ BEGIN_JUCE_NAMESPACE MD5::MD5() { zerostruct (result); } MD5::MD5 (const MD5& other) { memcpy (result, other.result, sizeof (result)); } MD5& MD5::operator= (const MD5& other) { memcpy (result, other.result, sizeof (result)); return *this; } MD5::MD5 (const MemoryBlock& data) { ProcessContext context; context.processBlock (data.getData(), data.getSize()); context.finish (result); } MD5::MD5 (const void* data, const size_t numBytes) { ProcessContext context; context.processBlock (data, numBytes); context.finish (result); } MD5::MD5 (const String& text) { ProcessContext context; String::CharPointerType t (text.getCharPointer()); while (! t.isEmpty()) { // force the string into integer-sized unicode characters, to try to make it // get the same results on all platforms + compilers. uint32 unicodeChar = ByteOrder::swapIfBigEndian ((uint32) t.getAndAdvance()); context.processBlock (&unicodeChar, sizeof (unicodeChar)); } context.finish (result); } void MD5::processStream (InputStream& input, int64 numBytesToRead) { ProcessContext context; if (numBytesToRead < 0) numBytesToRead = std::numeric_limits::max(); while (numBytesToRead > 0) { uint8 tempBuffer [512]; const int bytesRead = input.read (tempBuffer, (int) jmin (numBytesToRead, (int64) sizeof (tempBuffer))); if (bytesRead <= 0) break; numBytesToRead -= bytesRead; context.processBlock (tempBuffer, bytesRead); } context.finish (result); } MD5::MD5 (InputStream& input, int64 numBytesToRead) { processStream (input, numBytesToRead); } MD5::MD5 (const File& file) { FileInputStream fin (file); if (fin.getStatus().wasOk()) processStream (fin, -1); else zerostruct (result); } MD5::~MD5() { } namespace MD5Functions { void encode (void* const output, const void* const input, const int numBytes) noexcept { for (int i = 0; i < (numBytes >> 2); ++i) static_cast (output)[i] = ByteOrder::swapIfBigEndian (static_cast (input) [i]); } inline uint32 rotateLeft (const uint32 x, const uint32 n) noexcept { return (x << n) | (x >> (32 - n)); } inline uint32 F (const uint32 x, const uint32 y, const uint32 z) noexcept { return (x & y) | (~x & z); } inline uint32 G (const uint32 x, const uint32 y, const uint32 z) noexcept { return (x & z) | (y & ~z); } inline uint32 H (const uint32 x, const uint32 y, const uint32 z) noexcept { return x ^ y ^ z; } inline uint32 I (const uint32 x, const uint32 y, const uint32 z) noexcept { return y ^ (x | ~z); } void FF (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) noexcept { a += F (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } void GG (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) noexcept { a += G (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } void HH (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) noexcept { a += H (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } void II (uint32& a, const uint32 b, const uint32 c, const uint32 d, const uint32 x, const uint32 s, const uint32 ac) noexcept { a += I (b, c, d) + x + ac; a = rotateLeft (a, s) + b; } } MD5::ProcessContext::ProcessContext() { state[0] = 0x67452301; state[1] = 0xefcdab89; state[2] = 0x98badcfe; state[3] = 0x10325476; count[0] = 0; count[1] = 0; } void MD5::ProcessContext::processBlock (const void* const data, const size_t dataSize) { int bufferPos = ((count[0] >> 3) & 0x3F); count[0] += (uint32) (dataSize << 3); if (count[0] < ((uint32) dataSize << 3)) count[1]++; count[1] += (uint32) (dataSize >> 29); const size_t spaceLeft = 64 - bufferPos; size_t i = 0; if (dataSize >= spaceLeft) { memcpy (buffer + bufferPos, data, spaceLeft); transform (buffer); for (i = spaceLeft; i + 64 <= dataSize; i += 64) transform (static_cast (data) + i); bufferPos = 0; } memcpy (buffer + bufferPos, static_cast (data) + i, dataSize - i); } void MD5::ProcessContext::finish (void* const result) { unsigned char encodedLength[8]; MD5Functions::encode (encodedLength, count, 8); // Pad out to 56 mod 64. const int index = (uint32) ((count[0] >> 3) & 0x3f); const int paddingLength = (index < 56) ? (56 - index) : (120 - index); uint8 paddingBuffer[64] = { 0x80 }; // first byte is 0x80, remaining bytes are zero. processBlock (paddingBuffer, paddingLength); processBlock (encodedLength, 8); MD5Functions::encode (result, state, 16); zerostruct (buffer); } void MD5::ProcessContext::transform (const void* const bufferToTransform) { using namespace MD5Functions; uint32 a = state[0]; uint32 b = state[1]; uint32 c = state[2]; uint32 d = state[3]; uint32 x[16]; encode (x, bufferToTransform, 64); enum Constants { S11 = 7, S12 = 12, S13 = 17, S14 = 22, S21 = 5, S22 = 9, S23 = 14, S24 = 20, S31 = 4, S32 = 11, S33 = 16, S34 = 23, S41 = 6, S42 = 10, S43 = 15, S44 = 21 }; FF (a, b, c, d, x[ 0], S11, 0xd76aa478); FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); FF (c, d, a, b, x[ 2], S13, 0x242070db); FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); FF (d, a, b, c, x[ 5], S12, 0x4787c62a); FF (c, d, a, b, x[ 6], S13, 0xa8304613); FF (b, c, d, a, x[ 7], S14, 0xfd469501); FF (a, b, c, d, x[ 8], S11, 0x698098d8); FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); FF (c, d, a, b, x[10], S13, 0xffff5bb1); FF (b, c, d, a, x[11], S14, 0x895cd7be); FF (a, b, c, d, x[12], S11, 0x6b901122); FF (d, a, b, c, x[13], S12, 0xfd987193); FF (c, d, a, b, x[14], S13, 0xa679438e); FF (b, c, d, a, x[15], S14, 0x49b40821); GG (a, b, c, d, x[ 1], S21, 0xf61e2562); GG (d, a, b, c, x[ 6], S22, 0xc040b340); GG (c, d, a, b, x[11], S23, 0x265e5a51); GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); GG (a, b, c, d, x[ 5], S21, 0xd62f105d); GG (d, a, b, c, x[10], S22, 0x02441453); GG (c, d, a, b, x[15], S23, 0xd8a1e681); GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); GG (d, a, b, c, x[14], S22, 0xc33707d6); GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); GG (b, c, d, a, x[ 8], S24, 0x455a14ed); GG (a, b, c, d, x[13], S21, 0xa9e3e905); GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); GG (c, d, a, b, x[ 7], S23, 0x676f02d9); GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); HH (a, b, c, d, x[ 5], S31, 0xfffa3942); HH (d, a, b, c, x[ 8], S32, 0x8771f681); HH (c, d, a, b, x[11], S33, 0x6d9d6122); HH (b, c, d, a, x[14], S34, 0xfde5380c); HH (a, b, c, d, x[ 1], S31, 0xa4beea44); HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); HH (b, c, d, a, x[10], S34, 0xbebfbc70); HH (a, b, c, d, x[13], S31, 0x289b7ec6); HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); HH (b, c, d, a, x[ 6], S34, 0x04881d05); HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); HH (d, a, b, c, x[12], S32, 0xe6db99e5); HH (c, d, a, b, x[15], S33, 0x1fa27cf8); HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); II (a, b, c, d, x[ 0], S41, 0xf4292244); II (d, a, b, c, x[ 7], S42, 0x432aff97); II (c, d, a, b, x[14], S43, 0xab9423a7); II (b, c, d, a, x[ 5], S44, 0xfc93a039); II (a, b, c, d, x[12], S41, 0x655b59c3); II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); II (c, d, a, b, x[10], S43, 0xffeff47d); II (b, c, d, a, x[ 1], S44, 0x85845dd1); II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); II (d, a, b, c, x[15], S42, 0xfe2ce6e0); II (c, d, a, b, x[ 6], S43, 0xa3014314); II (b, c, d, a, x[13], S44, 0x4e0811a1); II (a, b, c, d, x[ 4], S41, 0xf7537e82); II (d, a, b, c, x[11], S42, 0xbd3af235); II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); II (b, c, d, a, x[ 9], S44, 0xeb86d391); state[0] += a; state[1] += b; state[2] += c; state[3] += d; zerostruct (x); } MemoryBlock MD5::getRawChecksumData() const { return MemoryBlock (result, sizeof (result)); } String MD5::toHexString() const { return String::toHexString (result, sizeof (result), 0); } bool MD5::operator== (const MD5& other) const { return memcmp (result, other.result, sizeof (result)) == 0; } bool MD5::operator!= (const MD5& other) const { return ! operator== (other); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MD5.cpp ***/ /*** Start of inlined file: juce_Primes.cpp ***/ BEGIN_JUCE_NAMESPACE namespace PrimesHelpers { void createSmallSieve (const int numBits, BigInteger& result) { result.setBit (numBits); result.clearBit (numBits); // to enlarge the array result.setBit (0); int n = 2; do { for (int i = n + n; i < numBits; i += n) result.setBit (i); n = result.findNextClearBit (n + 1); } while (n <= (numBits >> 1)); } void bigSieve (const BigInteger& base, const int numBits, BigInteger& result, const BigInteger& smallSieve, const int smallSieveSize) { jassert (! base[0]); // must be even! result.setBit (numBits); result.clearBit (numBits); // to enlarge the array int index = smallSieve.findNextClearBit (0); do { const int prime = (index << 1) + 1; BigInteger r (base), remainder; r.divideBy (prime, remainder); int i = prime - remainder.getBitRangeAsInt (0, 32); if (r.isZero()) i += prime; if ((i & 1) == 0) i += prime; i = (i - 1) >> 1; while (i < numBits) { result.setBit (i); i += prime; } index = smallSieve.findNextClearBit (index + 1); } while (index < smallSieveSize); } bool findCandidate (const BigInteger& base, const BigInteger& sieve, const int numBits, BigInteger& result, const int certainty) { for (int i = 0; i < numBits; ++i) { if (! sieve[i]) { result = base + (unsigned int) ((i << 1) + 1); if (Primes::isProbablyPrime (result, certainty)) return true; } } return false; } bool passesMillerRabin (const BigInteger& n, int iterations) { const BigInteger one (1), two (2); const BigInteger nMinusOne (n - one); BigInteger d (nMinusOne); const int s = d.findNextSetBit (0); d >>= s; BigInteger smallPrimes; int numBitsInSmallPrimes = 0; for (;;) { numBitsInSmallPrimes += 256; createSmallSieve (numBitsInSmallPrimes, smallPrimes); const int numPrimesFound = numBitsInSmallPrimes - smallPrimes.countNumberOfSetBits(); if (numPrimesFound > iterations + 1) break; } int smallPrime = 2; while (--iterations >= 0) { smallPrime = smallPrimes.findNextClearBit (smallPrime + 1); BigInteger r (smallPrime); r.exponentModulo (d, n); if (r != one && r != nMinusOne) { for (int j = 0; j < s; ++j) { r.exponentModulo (two, n); if (r == nMinusOne) break; } if (r != nMinusOne) return false; } } return true; } } BigInteger Primes::createProbablePrime (const int bitLength, const int certainty, const int* randomSeeds, int numRandomSeeds) { using namespace PrimesHelpers; int defaultSeeds [16]; if (numRandomSeeds <= 0) { randomSeeds = defaultSeeds; numRandomSeeds = numElementsInArray (defaultSeeds); Random r; for (int j = 10; --j >= 0;) { r.setSeedRandomly(); for (int i = numRandomSeeds; --i >= 0;) defaultSeeds[i] ^= r.nextInt() ^ Random::getSystemRandom().nextInt(); } } BigInteger smallSieve; const int smallSieveSize = 15000; createSmallSieve (smallSieveSize, smallSieve); BigInteger p; for (int i = numRandomSeeds; --i >= 0;) { BigInteger p2; Random r (randomSeeds[i]); r.fillBitsRandomly (p2, 0, bitLength); p ^= p2; } p.setBit (bitLength - 1); p.clearBit (0); const int searchLen = jmax (1024, (bitLength / 20) * 64); while (p.getHighestBit() < bitLength) { p += 2 * searchLen; BigInteger sieve; bigSieve (p, searchLen, sieve, smallSieve, smallSieveSize); BigInteger candidate; if (findCandidate (p, sieve, searchLen, candidate, certainty)) return candidate; } jassertfalse; return BigInteger(); } bool Primes::isProbablyPrime (const BigInteger& number, const int certainty) { using namespace PrimesHelpers; if (! number[0]) return false; if (number.getHighestBit() <= 10) { const int num = number.getBitRangeAsInt (0, 10); for (int i = num / 2; --i > 1;) if (num % i == 0) return false; return true; } else { if (number.findGreatestCommonDivisor (2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23) != 1) return false; return passesMillerRabin (number, certainty); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Primes.cpp ***/ /*** Start of inlined file: juce_RSAKey.cpp ***/ BEGIN_JUCE_NAMESPACE RSAKey::RSAKey() { } RSAKey::RSAKey (const String& s) { if (s.containsChar (',')) { part1.parseString (s.upToFirstOccurrenceOf (",", false, false), 16); part2.parseString (s.fromFirstOccurrenceOf (",", false, false), 16); } else { // the string needs to be two hex numbers, comma-separated.. jassertfalse; } } RSAKey::~RSAKey() { } bool RSAKey::operator== (const RSAKey& other) const noexcept { return part1 == other.part1 && part2 == other.part2; } bool RSAKey::operator!= (const RSAKey& other) const noexcept { return ! operator== (other); } String RSAKey::toString() const { return part1.toString (16) + "," + part2.toString (16); } bool RSAKey::applyToValue (BigInteger& value) const { if (part1.isZero() || part2.isZero() || value <= 0) { jassertfalse; // using an uninitialised key value.clear(); return false; } BigInteger result; while (! value.isZero()) { result *= part2; BigInteger remainder; value.divideBy (part2, remainder); remainder.exponentModulo (part1, part2); result += remainder; } value.swapWith (result); return true; } BigInteger RSAKey::findBestCommonDivisor (const BigInteger& p, const BigInteger& q) { // try 3, 5, 9, 17, etc first because these only contain 2 bits and so // are fast to divide + multiply for (int i = 2; i <= 65536; i *= 2) { const BigInteger e (1 + i); if (e.findGreatestCommonDivisor (p).isOne() && e.findGreatestCommonDivisor (q).isOne()) return e; } BigInteger e (4); while (! (e.findGreatestCommonDivisor (p).isOne() && e.findGreatestCommonDivisor (q).isOne())) ++e; return e; } void RSAKey::createKeyPair (RSAKey& publicKey, RSAKey& privateKey, const int numBits, const int* randomSeeds, const int numRandomSeeds) { jassert (numBits > 16); // not much point using less than this.. jassert (numRandomSeeds == 0 || numRandomSeeds >= 2); // you need to provide plenty of seeds here! BigInteger p (Primes::createProbablePrime (numBits / 2, 30, randomSeeds, numRandomSeeds / 2)); BigInteger q (Primes::createProbablePrime (numBits - numBits / 2, 30, randomSeeds == nullptr ? 0 : (randomSeeds + numRandomSeeds / 2), numRandomSeeds - numRandomSeeds / 2)); const BigInteger n (p * q); const BigInteger m (--p * --q); const BigInteger e (findBestCommonDivisor (p, q)); BigInteger d (e); d.inverseModulo (m); publicKey.part1 = e; publicKey.part2 = n; privateKey.part1 = d; privateKey.part2 = n; } END_JUCE_NAMESPACE /*** End of inlined file: juce_RSAKey.cpp ***/ /*** Start of inlined file: juce_InputStream.cpp ***/ BEGIN_JUCE_NAMESPACE char InputStream::readByte() { char temp = 0; read (&temp, 1); return temp; } bool InputStream::readBool() { return readByte() != 0; } short InputStream::readShort() { char temp[2]; if (read (temp, 2) == 2) return (short) ByteOrder::littleEndianShort (temp); return 0; } short InputStream::readShortBigEndian() { char temp[2]; if (read (temp, 2) == 2) return (short) ByteOrder::bigEndianShort (temp); return 0; } int InputStream::readInt() { char temp[4]; if (read (temp, 4) == 4) return (int) ByteOrder::littleEndianInt (temp); return 0; } int InputStream::readIntBigEndian() { char temp[4]; if (read (temp, 4) == 4) return (int) ByteOrder::bigEndianInt (temp); return 0; } int InputStream::readCompressedInt() { const unsigned char sizeByte = readByte(); if (sizeByte == 0) return 0; const int numBytes = (sizeByte & 0x7f); if (numBytes > 4) { jassertfalse; // trying to read corrupt data - this method must only be used // to read data that was written by OutputStream::writeCompressedInt() return 0; } char bytes[4] = { 0, 0, 0, 0 }; if (read (bytes, numBytes) != numBytes) return 0; const int num = (int) ByteOrder::littleEndianInt (bytes); return (sizeByte >> 7) ? -num : num; } int64 InputStream::readInt64() { union { uint8 asBytes[8]; uint64 asInt64; } n; if (read (n.asBytes, 8) == 8) return (int64) ByteOrder::swapIfBigEndian (n.asInt64); return 0; } int64 InputStream::readInt64BigEndian() { union { uint8 asBytes[8]; uint64 asInt64; } n; if (read (n.asBytes, 8) == 8) return (int64) ByteOrder::swapIfLittleEndian (n.asInt64); return 0; } float InputStream::readFloat() { // the union below relies on these types being the same size... static_jassert (sizeof (int32) == sizeof (float)); union { int32 asInt; float asFloat; } n; n.asInt = (int32) readInt(); return n.asFloat; } float InputStream::readFloatBigEndian() { union { int32 asInt; float asFloat; } n; n.asInt = (int32) readIntBigEndian(); return n.asFloat; } double InputStream::readDouble() { union { int64 asInt; double asDouble; } n; n.asInt = readInt64(); return n.asDouble; } double InputStream::readDoubleBigEndian() { union { int64 asInt; double asDouble; } n; n.asInt = readInt64BigEndian(); return n.asDouble; } String InputStream::readString() { MemoryBlock buffer (256); char* data = static_cast (buffer.getData()); size_t i = 0; while ((data[i] = readByte()) != 0) { if (++i >= buffer.getSize()) { buffer.setSize (buffer.getSize() + 512); data = static_cast (buffer.getData()); } } return String::fromUTF8 (data, (int) i); } String InputStream::readNextLine() { MemoryBlock buffer (256); char* data = static_cast (buffer.getData()); size_t i = 0; while ((data[i] = readByte()) != 0) { if (data[i] == '\n') break; if (data[i] == '\r') { const int64 lastPos = getPosition(); if (readByte() != '\n') setPosition (lastPos); break; } if (++i >= buffer.getSize()) { buffer.setSize (buffer.getSize() + 512); data = static_cast (buffer.getData()); } } return String::fromUTF8 (data, (int) i); } int InputStream::readIntoMemoryBlock (MemoryBlock& block, int numBytes) { MemoryOutputStream mo (block, true); return mo.writeFromInputStream (*this, numBytes); } String InputStream::readEntireStreamAsString() { MemoryOutputStream mo; mo.writeFromInputStream (*this, -1); return mo.toString(); } void InputStream::skipNextBytes (int64 numBytesToSkip) { if (numBytesToSkip > 0) { const int skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384); HeapBlock temp (skipBufferSize); while (numBytesToSkip > 0 && ! isExhausted()) numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize)); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InputStream.cpp ***/ /*** Start of inlined file: juce_OutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE #if JUCE_DEBUG struct DanglingStreamChecker { DanglingStreamChecker() {} ~DanglingStreamChecker() { /* It's always a bad idea to leak any object, but if you're leaking output streams, then there's a good chance that you're failing to flush a file to disk properly, which could result in corrupted data and other similar nastiness.. */ jassert (activeStreams.size() == 0); } Array activeStreams; }; static DanglingStreamChecker danglingStreamChecker; #endif OutputStream::OutputStream() : newLineString (NewLine::getDefault()) { #if JUCE_DEBUG danglingStreamChecker.activeStreams.add (this); #endif } OutputStream::~OutputStream() { #if JUCE_DEBUG danglingStreamChecker.activeStreams.removeValue (this); #endif } void OutputStream::writeBool (const bool b) { writeByte (b ? (char) 1 : (char) 0); } void OutputStream::writeByte (char byte) { write (&byte, 1); } void OutputStream::writeRepeatedByte (uint8 byte, int numTimesToRepeat) { while (--numTimesToRepeat >= 0) writeByte (byte); } void OutputStream::writeShort (short value) { const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); write (&v, 2); } void OutputStream::writeShortBigEndian (short value) { const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); write (&v, 2); } void OutputStream::writeInt (int value) { const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); write (&v, 4); } void OutputStream::writeIntBigEndian (int value) { const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); write (&v, 4); } void OutputStream::writeCompressedInt (int value) { unsigned int un = (value < 0) ? (unsigned int) -value : (unsigned int) value; uint8 data[5]; int num = 0; while (un > 0) { data[++num] = (uint8) un; un >>= 8; } data[0] = (uint8) num; if (value < 0) data[0] |= 0x80; write (data, num + 1); } void OutputStream::writeInt64 (int64 value) { const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); write (&v, 8); } void OutputStream::writeInt64BigEndian (int64 value) { const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); write (&v, 8); } void OutputStream::writeFloat (float value) { union { int asInt; float asFloat; } n; n.asFloat = value; writeInt (n.asInt); } void OutputStream::writeFloatBigEndian (float value) { union { int asInt; float asFloat; } n; n.asFloat = value; writeIntBigEndian (n.asInt); } void OutputStream::writeDouble (double value) { union { int64 asInt; double asDouble; } n; n.asDouble = value; writeInt64 (n.asInt); } void OutputStream::writeDoubleBigEndian (double value) { union { int64 asInt; double asDouble; } n; n.asDouble = value; writeInt64BigEndian (n.asInt); } void OutputStream::writeString (const String& text) { // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind // if lots of large, persistent strings were to be written to streams). const int numBytes = text.getNumBytesAsUTF8() + 1; HeapBlock temp (numBytes); text.copyToUTF8 (temp, numBytes); write (temp, numBytes); } void OutputStream::writeText (const String& text, const bool asUTF16, const bool writeUTF16ByteOrderMark) { if (asUTF16) { if (writeUTF16ByteOrderMark) write ("\x0ff\x0fe", 2); String::CharPointerType src (text.getCharPointer()); bool lastCharWasReturn = false; for (;;) { const juce_wchar c = src.getAndAdvance(); if (c == 0) break; if (c == '\n' && ! lastCharWasReturn) writeShort ((short) '\r'); lastCharWasReturn = (c == L'\r'); writeShort ((short) c); } } else { const char* src = text.toUTF8(); const char* t = src; for (;;) { if (*t == '\n') { if (t > src) write (src, (int) (t - src)); write ("\r\n", 2); src = t + 1; } else if (*t == '\r') { if (t[1] == '\n') ++t; } else if (*t == 0) { if (t > src) write (src, (int) (t - src)); break; } ++t; } } } int OutputStream::writeFromInputStream (InputStream& source, int64 numBytesToWrite) { if (numBytesToWrite < 0) numBytesToWrite = std::numeric_limits::max(); int numWritten = 0; while (numBytesToWrite > 0 && ! source.isExhausted()) { char buffer [8192]; const int num = source.read (buffer, (int) jmin (numBytesToWrite, (int64) sizeof (buffer))); if (num <= 0) break; write (buffer, num); numBytesToWrite -= num; numWritten += num; } return numWritten; } void OutputStream::setNewLineString (const String& newLineString_) { newLineString = newLineString_; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int number) { return stream << String (number); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const double number) { return stream << String (number); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char character) { stream.writeByte (character); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* const text) { stream.write (text, (int) strlen (text)); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) { stream.write (data.getData(), (int) data.getSize()); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) { const ScopedPointer in (fileToRead.createInputStream()); if (in != nullptr) stream.writeFromInputStream (*in, -1); return stream; } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&) { return stream << stream.getNewLineString(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_OutputStream.cpp ***/ /*** Start of inlined file: juce_DirectoryIterator.cpp ***/ BEGIN_JUCE_NAMESPACE DirectoryIterator::DirectoryIterator (const File& directory, bool isRecursive_, const String& wildCard_, const int whatToLookFor_) : fileFinder (directory, isRecursive_ ? "*" : wildCard_), wildCard (wildCard_), path (File::addTrailingSeparator (directory.getFullPathName())), index (-1), totalNumFiles (-1), whatToLookFor (whatToLookFor_), isRecursive (isRecursive_), hasBeenAdvanced (false) { // you have to specify the type of files you're looking for! jassert ((whatToLookFor_ & (File::findFiles | File::findDirectories)) != 0); jassert (whatToLookFor_ > 0 && whatToLookFor_ <= 7); } DirectoryIterator::~DirectoryIterator() { } bool DirectoryIterator::next() { return next (nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } bool DirectoryIterator::next (bool* const isDirResult, bool* const isHiddenResult, int64* const fileSize, Time* const modTime, Time* const creationTime, bool* const isReadOnly) { hasBeenAdvanced = true; if (subIterator != nullptr) { if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) return true; subIterator = nullptr; } String filename; bool isDirectory, isHidden; while (fileFinder.next (filename, &isDirectory, &isHidden, fileSize, modTime, creationTime, isReadOnly)) { ++index; if (! filename.containsOnly (".")) { const File fileFound (path + filename, 0); bool matches = false; if (isDirectory) { if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) subIterator = new DirectoryIterator (fileFound, true, wildCard, whatToLookFor); matches = (whatToLookFor & File::findDirectories) != 0; } else { matches = (whatToLookFor & File::findFiles) != 0; } // if recursive, we're not relying on the OS iterator to do the wildcard match, so do it now.. if (matches && isRecursive) matches = filename.matchesWildcard (wildCard, ! File::areFileNamesCaseSensitive()); if (matches && (whatToLookFor & File::ignoreHiddenFiles) != 0) matches = ! isHidden; if (matches) { currentFile = fileFound; if (isHiddenResult != nullptr) *isHiddenResult = isHidden; if (isDirResult != nullptr) *isDirResult = isDirectory; return true; } else if (subIterator != nullptr) { return next(); } } } return false; } const File& DirectoryIterator::getFile() const { if (subIterator != nullptr && subIterator->hasBeenAdvanced) return subIterator->getFile(); // You need to call DirectoryIterator::next() before asking it for the file that it found! jassert (hasBeenAdvanced); return currentFile; } float DirectoryIterator::getEstimatedProgress() const { if (totalNumFiles < 0) totalNumFiles = File (path).getNumberOfChildFiles (File::findFilesAndDirectories); if (totalNumFiles <= 0) return 0.0f; const float detailedIndex = (subIterator != nullptr) ? index + subIterator->getEstimatedProgress() : (float) index; return detailedIndex / totalNumFiles; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DirectoryIterator.cpp ***/ /*** Start of inlined file: juce_File.cpp ***/ #if ! JUCE_WINDOWS #include #endif BEGIN_JUCE_NAMESPACE File::File (const String& fullPathName) : fullPath (parseAbsolutePath (fullPathName)) { } File::File (const String& path, int) : fullPath (path) { } File File::createFileWithoutCheckingPath (const String& path) { return File (path, 0); } File::File (const File& other) : fullPath (other.fullPath) { } File& File::operator= (const String& newPath) { fullPath = parseAbsolutePath (newPath); return *this; } File& File::operator= (const File& other) { fullPath = other.fullPath; return *this; } const File File::nonexistent; String File::parseAbsolutePath (const String& p) { if (p.isEmpty()) return String::empty; #if JUCE_WINDOWS // Windows.. String path (p.replaceCharacter ('/', '\\')); if (path.startsWithChar (File::separator)) { if (path[1] != File::separator) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassertfalse; path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; } } else if (! path.containsChar (':')) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassertfalse; return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #else // Mac or Linux.. // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here // to catch anyone who's trying to run code that was written on Windows with hard-coded path names. // If that's why you've ended up here, use File::getChildFile() to build your paths instead. jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\'))); String path (p); if (path.startsWithChar ('~')) { if (path[1] == File::separator || path[1] == 0) { // expand a name of the form "~/abc" path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + path.substring (1); } else { // expand a name of type "~dave/abc" const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); struct passwd* const pw = getpwnam (userName.toUTF8()); if (pw != nullptr) path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); } } else if (! path.startsWithChar (File::separator)) { /* When you supply a raw string to the File object constructor, it must be an absolute path. If you're trying to parse a string that may be either a relative path or an absolute path, you MUST provide a context against which the partial path can be evaluated - you can do this by simply using File::getChildFile() instead of the File constructor. E.g. saying "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute path if that's what was supplied, or would evaluate a partial path relative to the CWD. */ jassert (path.startsWith ("./") || path.startsWith ("../")); // (assume that a path "./xyz" is deliberately intended to be relative to the CWD) return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #endif while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string. path = path.dropLastCharacters (1); return path; } String File::addTrailingSeparator (const String& path) { return path.endsWithChar (File::separator) ? path : path + File::separator; } #if JUCE_LINUX #define NAMES_ARE_CASE_SENSITIVE 1 #endif bool File::areFileNamesCaseSensitive() { #if NAMES_ARE_CASE_SENSITIVE return true; #else return false; #endif } bool File::operator== (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath == other.fullPath; #else return fullPath.equalsIgnoreCase (other.fullPath); #endif } bool File::operator!= (const File& other) const { return ! operator== (other); } bool File::operator< (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath < other.fullPath; #else return fullPath.compareIgnoreCase (other.fullPath) < 0; #endif } bool File::operator> (const File& other) const { #if NAMES_ARE_CASE_SENSITIVE return fullPath > other.fullPath; #else return fullPath.compareIgnoreCase (other.fullPath) > 0; #endif } bool File::setReadOnly (const bool shouldBeReadOnly, const bool applyRecursively) const { bool worked = true; if (applyRecursively && isDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked; } return setFileReadOnlyInternal (shouldBeReadOnly) && worked; } bool File::deleteRecursively() const { bool worked = true; if (isDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles.getReference(i).deleteRecursively() && worked; } return deleteFile() && worked; } bool File::moveFileTo (const File& newFile) const { if (newFile.fullPath == fullPath) return true; #if ! NAMES_ARE_CASE_SENSITIVE if (*this != newFile) #endif if (! newFile.deleteFile()) return false; return moveInternal (newFile); } bool File::copyFileTo (const File& newFile) const { return (*this == newFile) || (exists() && newFile.deleteFile() && copyInternal (newFile)); } bool File::copyDirectoryTo (const File& newDirectory) const { if (isDirectory() && newDirectory.createDirectory()) { Array subFiles; findChildFiles (subFiles, File::findFiles, false); int i; for (i = 0; i < subFiles.size(); ++i) if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) return false; subFiles.clear(); findChildFiles (subFiles, File::findDirectories, false); for (i = 0; i < subFiles.size(); ++i) if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) return false; return true; } return false; } String File::getPathUpToLastSlash() const { const int lastSlash = fullPath.lastIndexOfChar (separator); if (lastSlash > 0) return fullPath.substring (0, lastSlash); else if (lastSlash == 0) return separatorString; else return fullPath; } File File::getParentDirectory() const { return File (getPathUpToLastSlash(), (int) 0); } String File::getFileName() const { return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); } int File::hashCode() const { return fullPath.hashCode(); } int64 File::hashCode64() const { return fullPath.hashCode64(); } String File::getFileNameWithoutExtension() const { const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; const int lastDot = fullPath.lastIndexOfChar ('.'); if (lastDot > lastSlash) return fullPath.substring (lastSlash, lastDot); else return fullPath.substring (lastSlash); } bool File::isAChildOf (const File& potentialParent) const { if (potentialParent == File::nonexistent) return false; const String ourPath (getPathUpToLastSlash()); #if NAMES_ARE_CASE_SENSITIVE if (potentialParent.fullPath == ourPath) #else if (potentialParent.fullPath.equalsIgnoreCase (ourPath)) #endif { return true; } else if (potentialParent.fullPath.length() >= ourPath.length()) { return false; } else { return getParentDirectory().isAChildOf (potentialParent); } } bool File::isAbsolutePath (const String& path) { return path.startsWithChar (separator) #if JUCE_WINDOWS || (path.isNotEmpty() && path[1] == ':'); #else || path.startsWithChar ('~'); #endif } File File::getChildFile (String relativePath) const { if (isAbsolutePath (relativePath)) return File (relativePath); String path (fullPath); // It's relative, so remove any ../ or ./ bits at the start.. if (relativePath[0] == '.') { #if JUCE_WINDOWS relativePath = relativePath.replaceCharacter ('/', '\\').trimStart(); #else relativePath = relativePath.trimStart(); #endif while (relativePath[0] == '.') { if (relativePath[1] == '.') { if (relativePath [2] == 0 || relativePath[2] == separator) { const int lastSlash = path.lastIndexOfChar (separator); if (lastSlash >= 0) path = path.substring (0, lastSlash); relativePath = relativePath.substring (3); } else { break; } } else if (relativePath[1] == separator) { relativePath = relativePath.substring (2); } else { break; } } } return File (addTrailingSeparator (path) + relativePath); } File File::getSiblingFile (const String& fileName) const { return getParentDirectory().getChildFile (fileName); } String File::descriptionOfSizeInBytes (const int64 bytes) { if (bytes == 1) return "1 byte"; else if (bytes < 1024) return String (bytes) + " bytes"; else if (bytes < 1024 * 1024) return String (bytes / 1024.0, 1) + " KB"; else if (bytes < 1024 * 1024 * 1024) return String (bytes / (1024.0 * 1024.0), 1) + " MB"; else return String (bytes / (1024.0 * 1024.0 * 1024.0), 1) + " GB"; } Result File::create() const { if (exists()) return Result::ok(); const File parentDir (getParentDirectory()); if (parentDir == *this) return Result::fail ("Cannot create parent directory"); Result r (parentDir.createDirectory()); if (r.wasOk()) { FileOutputStream fo (*this, 8); r = fo.getStatus(); } return r; } Result File::createDirectory() const { if (isDirectory()) return Result::ok(); const File parentDir (getParentDirectory()); if (parentDir == *this) return Result::fail ("Cannot create parent directory"); Result r (parentDir.createDirectory()); if (r.wasOk()) r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); return r; } const Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); } const Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); } const Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } bool File::setLastModificationTime (const Time& t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); } bool File::setLastAccessTime (const Time& t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); } bool File::setCreationTime (const Time& t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); } bool File::loadFileAsData (MemoryBlock& destBlock) const { if (! existsAsFile()) return false; FileInputStream in (*this); return getSize() == in.readIntoMemoryBlock (destBlock); } String File::loadFileAsString() const { if (! existsAsFile()) return String::empty; FileInputStream in (*this); return in.readEntireStreamAsString(); } int File::findChildFiles (Array& results, const int whatToLookFor, const bool searchRecursively, const String& wildCardPattern) const { DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); int total = 0; while (di.next()) { results.add (di.getFile()); ++total; } return total; } int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const { DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); int total = 0; while (di.next()) ++total; return total; } bool File::containsSubDirectories() const { if (isDirectory()) { DirectoryIterator di (*this, false, "*", findDirectories); return di.next(); } return false; } File File::getNonexistentChildFile (const String& prefix_, const String& suffix, bool putNumbersInBrackets) const { File f (getChildFile (prefix_ + suffix)); if (f.exists()) { int num = 2; String prefix (prefix_); // remove any bracketed numbers that may already be on the end.. if (prefix.trim().endsWithChar (')')) { putNumbersInBrackets = true; const int openBracks = prefix.lastIndexOfChar ('('); const int closeBracks = prefix.lastIndexOfChar (')'); if (openBracks > 0 && closeBracks > openBracks && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789")) { num = prefix.substring (openBracks + 1, closeBracks).getIntValue() + 1; prefix = prefix.substring (0, openBracks); } } // also use brackets if it ends in a digit. putNumbersInBrackets = putNumbersInBrackets || CharacterFunctions::isDigit (prefix.getLastCharacter()); do { String newName (prefix); if (putNumbersInBrackets) newName << '(' << num++ << ')'; else newName << num++; f = getChildFile (newName + suffix); } while (f.exists()); } return f; } File File::getNonexistentSibling (const bool putNumbersInBrackets) const { if (exists()) return getParentDirectory() .getNonexistentChildFile (getFileNameWithoutExtension(), getFileExtension(), putNumbersInBrackets); return *this; } String File::getFileExtension() const { const int indexOfDot = fullPath.lastIndexOfChar ('.'); if (indexOfDot > fullPath.lastIndexOfChar (separator)) return fullPath.substring (indexOfDot); return String::empty; } bool File::hasFileExtension (const String& possibleSuffix) const { if (possibleSuffix.isEmpty()) return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator); const int semicolon = possibleSuffix.indexOfChar (0, ';'); if (semicolon >= 0) { return hasFileExtension (possibleSuffix.substring (0, semicolon).trimEnd()) || hasFileExtension (possibleSuffix.substring (semicolon + 1).trimStart()); } else { if (fullPath.endsWithIgnoreCase (possibleSuffix)) { if (possibleSuffix.startsWithChar ('.')) return true; const int dotPos = fullPath.length() - possibleSuffix.length() - 1; if (dotPos >= 0) return fullPath [dotPos] == '.'; } } return false; } File File::withFileExtension (const String& newExtension) const { if (fullPath.isEmpty()) return File::nonexistent; String filePart (getFileName()); int i = filePart.lastIndexOfChar ('.'); if (i >= 0) filePart = filePart.substring (0, i); if (newExtension.isNotEmpty() && ! newExtension.startsWithChar ('.')) filePart << '.'; return getSiblingFile (filePart + newExtension); } bool File::startAsProcess (const String& parameters) const { return exists() && PlatformUtilities::openDocument (fullPath, parameters); } FileInputStream* File::createInputStream() const { if (existsAsFile()) return new FileInputStream (*this); return nullptr; } FileOutputStream* File::createOutputStream (const int bufferSize) const { ScopedPointer out (new FileOutputStream (*this, bufferSize)); if (out->failedToOpen()) return nullptr; return out.release(); } bool File::appendData (const void* const dataToAppend, const int numberOfBytes) const { if (numberOfBytes > 0) { const ScopedPointer out (createOutputStream()); if (out == 0) return false; out->write (dataToAppend, numberOfBytes); } return true; } bool File::replaceWithData (const void* const dataToWrite, const int numberOfBytes) const { jassert (numberOfBytes >= 0); // a negative number of bytes?? if (numberOfBytes <= 0) return deleteFile(); TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); tempFile.getFile().appendData (dataToWrite, numberOfBytes); return tempFile.overwriteTargetFileWithTemporary(); } bool File::appendText (const String& text, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { const ScopedPointer out (createOutputStream()); if (out != nullptr) { out->writeText (text, asUnicode, writeUnicodeHeaderBytes); return true; } return false; } bool File::replaceWithText (const String& textToWrite, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes); return tempFile.overwriteTargetFileWithTemporary(); } bool File::hasIdenticalContentTo (const File& other) const { if (other == *this) return true; if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile()) { FileInputStream in1 (*this), in2 (other); const int bufferSize = 4096; HeapBlock buffer1 (bufferSize), buffer2 (bufferSize); for (;;) { const int num1 = in1.read (buffer1, bufferSize); const int num2 = in2.read (buffer2, bufferSize); if (num1 != num2) break; if (num1 <= 0) return true; if (memcmp (buffer1, buffer2, num1) != 0) break; } } return false; } String File::createLegalPathName (const String& original) { String s (original); String start; if (s[1] == ':') { start = s.substring (0, 2); s = s.substring (2); } return start + s.removeCharacters ("\"#@,;:<>*^|?") .substring (0, 1024); } String File::createLegalFileName (const String& original) { String s (original.removeCharacters ("\"#@,;:<>*^|?\\/")); const int maxLength = 128; // only the length of the filename, not the whole path const int len = s.length(); if (len > maxLength) { const int lastDot = s.lastIndexOfChar ('.'); if (lastDot > jmax (0, len - 12)) { s = s.substring (0, maxLength - (len - lastDot)) + s.substring (lastDot); } else { s = s.substring (0, maxLength); } } return s; } String File::getRelativePathFrom (const File& dir) const { String thisPath (fullPath); while (thisPath.endsWithChar (separator)) thisPath = thisPath.dropLastCharacters (1); String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() : dir.fullPath)); const int len = jmin (thisPath.length(), dirPath.length()); int commonBitLength = 0; { String::CharPointerType thisPathIter (thisPath.getCharPointer()); String::CharPointerType dirPathIter (dirPath.getCharPointer()); for (int i = 0; i < len; ++i) { const juce_wchar c1 = thisPathIter.getAndAdvance(); const juce_wchar c2 = dirPathIter.getAndAdvance(); #if NAMES_ARE_CASE_SENSITIVE if (c1 != c2) #else if (c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2)) #endif break; ++commonBitLength; } } while (commonBitLength > 0 && thisPath [commonBitLength - 1] != File::separator) --commonBitLength; // if the only common bit is the root, then just return the full path.. if (commonBitLength <= 0 || (commonBitLength == 1 && thisPath [1] == File::separator)) return fullPath; thisPath = thisPath.substring (commonBitLength); dirPath = dirPath.substring (commonBitLength); while (dirPath.isNotEmpty()) { #if JUCE_WINDOWS thisPath = "..\\" + thisPath; #else thisPath = "../" + thisPath; #endif const int sep = dirPath.indexOfChar (separator); if (sep >= 0) dirPath = dirPath.substring (sep + 1); else dirPath = String::empty; } return thisPath; } File File::createTempFile (const String& fileNameEnding) { const File tempFile (getSpecialLocation (tempDirectory) .getChildFile ("temp_" + String (Random::getSystemRandom().nextInt())) .withFileExtension (fileNameEnding)); if (tempFile.exists()) return createTempFile (fileNameEnding); return tempFile; } #if JUCE_UNIT_TESTS class FileTests : public UnitTest { public: FileTests() : UnitTest ("Files") {} void runTest() { beginTest ("Reading"); const File home (File::getSpecialLocation (File::userHomeDirectory)); const File temp (File::getSpecialLocation (File::tempDirectory)); expect (! File::nonexistent.exists()); expect (home.isDirectory()); expect (home.exists()); expect (! home.existsAsFile()); expect (File::getSpecialLocation (File::userDocumentsDirectory).isDirectory()); expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory()); expect (File::getSpecialLocation (File::currentExecutableFile).exists()); expect (File::getSpecialLocation (File::currentApplicationFile).exists()); expect (File::getSpecialLocation (File::invokedExecutableFile).exists()); expect (home.getVolumeTotalSize() > 1024 * 1024); expect (home.getBytesFreeOnVolume() > 0); expect (! home.isHidden()); expect (home.isOnHardDisk()); expect (! home.isOnCDRomDrive()); expect (File::getCurrentWorkingDirectory().exists()); expect (home.setAsCurrentWorkingDirectory()); expect (File::getCurrentWorkingDirectory() == home); { Array roots; File::findFileSystemRoots (roots); expect (roots.size() > 0); int numRootsExisting = 0; for (int i = 0; i < roots.size(); ++i) if (roots[i].exists()) ++numRootsExisting; // (on windows, some of the drives may not contain media, so as long as at least one is ok..) expect (numRootsExisting > 0); } beginTest ("Writing"); File demoFolder (temp.getChildFile ("Juce UnitTests Temp Folder.folder")); expect (demoFolder.deleteRecursively()); expect (demoFolder.createDirectory()); expect (demoFolder.isDirectory()); expect (demoFolder.getParentDirectory() == temp); expect (temp.isDirectory()); { Array files; temp.findChildFiles (files, File::findFilesAndDirectories, false, "*"); expect (files.contains (demoFolder)); } { Array files; temp.findChildFiles (files, File::findDirectories, true, "*.folder"); expect (files.contains (demoFolder)); } File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false)); expect (tempFile.getFileExtension() == ".txt"); expect (tempFile.hasFileExtension (".txt")); expect (tempFile.hasFileExtension ("txt")); expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz")); expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo")); expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo")); expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx")); expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); expect (tempFile.hasWriteAccess()); { FileOutputStream fo (tempFile); fo.write ("0123456789", 10); } expect (tempFile.exists()); expect (tempFile.getSize() == 10); expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); expectEquals (tempFile.loadFileAsString(), String ("0123456789")); expect (! demoFolder.containsSubDirectories()); expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::separatorString + tempFile.getFileName()); expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::separatorString + ".." + File::separatorString + demoFolder.getParentDirectory().getFileName()); expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1); expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1); expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0); demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory(); expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1); expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2); expect (demoFolder.containsSubDirectories()); expect (tempFile.hasWriteAccess()); tempFile.setReadOnly (true); expect (! tempFile.hasWriteAccess()); tempFile.setReadOnly (false); expect (tempFile.hasWriteAccess()); Time t (Time::getCurrentTime()); tempFile.setLastModificationTime (t); Time t2 = tempFile.getLastModificationTime(); expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000); { MemoryBlock mb; tempFile.loadFileAsData (mb); expect (mb.getSize() == 10); expect (mb[0] == '0'); } beginTest ("Memory-mapped files"); { MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly); expect (mmf.getSize() == 10); expect (mmf.getData() != nullptr); expect (memcmp (mmf.getData(), "0123456789", 10) == 0); } { const File tempFile2 (tempFile.getNonexistentSibling (false)); expect (tempFile2.create()); expect (tempFile2.appendData ("xxxxxxxxxx", 10)); { MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); expect (mmf.getSize() == 10); expect (mmf.getData() != nullptr); memcpy (mmf.getData(), "abcdefghij", 10); } { MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); expect (mmf.getSize() == 10); expect (mmf.getData() != nullptr); expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0); } expect (tempFile2.deleteFile()); } beginTest ("More writing"); expect (tempFile.appendData ("abcdefghij", 10)); expect (tempFile.getSize() == 20); expect (tempFile.replaceWithData ("abcdefghij", 10)); expect (tempFile.getSize() == 10); File tempFile2 (tempFile.getNonexistentSibling (false)); expect (tempFile.copyFileTo (tempFile2)); expect (tempFile2.exists()); expect (tempFile2.hasIdenticalContentTo (tempFile)); expect (tempFile.deleteFile()); expect (! tempFile.exists()); expect (tempFile2.moveFileTo (tempFile)); expect (tempFile.exists()); expect (! tempFile2.exists()); expect (demoFolder.deleteRecursively()); expect (! demoFolder.exists()); } }; static FileTests fileUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_File.cpp ***/ /*** Start of inlined file: juce_FileInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE int64 juce_fileSetPosition (void* handle, int64 pos); FileInputStream::FileInputStream (const File& f) : file (f), fileHandle (nullptr), currentPosition (0), totalSize (0), status (Result::ok()), needToSeek (true) { openHandle(); } FileInputStream::~FileInputStream() { closeHandle(); } int64 FileInputStream::getTotalLength() { return totalSize; } int FileInputStream::read (void* buffer, int bytesToRead) { if (needToSeek) { if (juce_fileSetPosition (fileHandle, currentPosition) < 0) return 0; needToSeek = false; } const size_t num = readInternal (buffer, bytesToRead); currentPosition += num; return (int) num; } bool FileInputStream::isExhausted() { return currentPosition >= totalSize; } int64 FileInputStream::getPosition() { return currentPosition; } bool FileInputStream::setPosition (int64 pos) { pos = jlimit ((int64) 0, totalSize, pos); needToSeek |= (currentPosition != pos); currentPosition = pos; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileInputStream.cpp ***/ /*** Start of inlined file: juce_FileOutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE int64 juce_fileSetPosition (void* handle, int64 pos); FileOutputStream::FileOutputStream (const File& f, const int bufferSize_) : file (f), fileHandle (nullptr), status (Result::ok()), currentPosition (0), bufferSize (bufferSize_), bytesInBuffer (0), buffer (jmax (bufferSize_, 16)) { openHandle(); } FileOutputStream::~FileOutputStream() { flushBuffer(); flushInternal(); closeHandle(); } int64 FileOutputStream::getPosition() { return currentPosition; } bool FileOutputStream::setPosition (int64 newPosition) { if (newPosition != currentPosition) { flushBuffer(); currentPosition = juce_fileSetPosition (fileHandle, newPosition); } return newPosition == currentPosition; } bool FileOutputStream::flushBuffer() { bool ok = true; if (bytesInBuffer > 0) { ok = (writeInternal (buffer, bytesInBuffer) == bytesInBuffer); bytesInBuffer = 0; } return ok; } void FileOutputStream::flush() { flushBuffer(); flushInternal(); } bool FileOutputStream::write (const void* const src, const int numBytes) { if (bytesInBuffer + numBytes < bufferSize) { memcpy (buffer + bytesInBuffer, src, numBytes); bytesInBuffer += numBytes; currentPosition += numBytes; } else { if (! flushBuffer()) return false; if (numBytes < bufferSize) { memcpy (buffer + bytesInBuffer, src, numBytes); bytesInBuffer += numBytes; currentPosition += numBytes; } else { const int bytesWritten = writeInternal (src, numBytes); if (bytesWritten < 0) return false; currentPosition += bytesWritten; return bytesWritten == numBytes; } } return true; } void FileOutputStream::writeRepeatedByte (uint8 byte, int numBytes) { if (bytesInBuffer + numBytes < bufferSize) { memset (buffer + bytesInBuffer, byte, numBytes); bytesInBuffer += numBytes; currentPosition += numBytes; } else { OutputStream::writeRepeatedByte (byte, numBytes); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileOutputStream.cpp ***/ /*** Start of inlined file: juce_FileSearchPath.cpp ***/ BEGIN_JUCE_NAMESPACE FileSearchPath::FileSearchPath() { } FileSearchPath::FileSearchPath (const String& path) { init (path); } FileSearchPath::FileSearchPath (const FileSearchPath& other) : directories (other.directories) { } FileSearchPath::~FileSearchPath() { } FileSearchPath& FileSearchPath::operator= (const String& path) { init (path); return *this; } void FileSearchPath::init (const String& path) { directories.clear(); directories.addTokens (path, ";", "\""); directories.trim(); directories.removeEmptyStrings(); for (int i = directories.size(); --i >= 0;) directories.set (i, directories[i].unquoted()); } int FileSearchPath::getNumPaths() const { return directories.size(); } File FileSearchPath::operator[] (const int index) const { return File (directories [index]); } String FileSearchPath::toString() const { StringArray directories2 (directories); for (int i = directories2.size(); --i >= 0;) if (directories2[i].containsChar (';')) directories2.set (i, directories2[i].quoted()); return directories2.joinIntoString (";"); } void FileSearchPath::add (const File& dir, const int insertIndex) { directories.insert (insertIndex, dir.getFullPathName()); } void FileSearchPath::addIfNotAlreadyThere (const File& dir) { for (int i = 0; i < directories.size(); ++i) if (File (directories[i]) == dir) return; add (dir); } void FileSearchPath::remove (const int index) { directories.remove (index); } void FileSearchPath::addPath (const FileSearchPath& other) { for (int i = 0; i < other.getNumPaths(); ++i) addIfNotAlreadyThere (other[i]); } void FileSearchPath::removeRedundantPaths() { for (int i = directories.size(); --i >= 0;) { const File d1 (directories[i]); for (int j = directories.size(); --j >= 0;) { const File d2 (directories[j]); if ((i != j) && (d1.isAChildOf (d2) || d1 == d2)) { directories.remove (i); break; } } } } void FileSearchPath::removeNonExistentPaths() { for (int i = directories.size(); --i >= 0;) if (! File (directories[i]).isDirectory()) directories.remove (i); } int FileSearchPath::findChildFiles (Array& results, const int whatToLookFor, const bool searchRecursively, const String& wildCardPattern) const { int total = 0; for (int i = 0; i < directories.size(); ++i) total += operator[] (i).findChildFiles (results, whatToLookFor, searchRecursively, wildCardPattern); return total; } bool FileSearchPath::isFileInPath (const File& fileToCheck, const bool checkRecursively) const { for (int i = directories.size(); --i >= 0;) { const File d (directories[i]); if (checkRecursively) { if (fileToCheck.isAChildOf (d)) return true; } else { if (fileToCheck.getParentDirectory() == d) return true; } } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileSearchPath.cpp ***/ /*** Start of inlined file: juce_NamedPipe.cpp ***/ BEGIN_JUCE_NAMESPACE NamedPipe::NamedPipe() : internal (nullptr) { } NamedPipe::~NamedPipe() { close(); } bool NamedPipe::openExisting (const String& pipeName) { currentPipeName = pipeName; return openInternal (pipeName, false); } bool NamedPipe::createNewPipe (const String& pipeName) { currentPipeName = pipeName; return openInternal (pipeName, true); } bool NamedPipe::isOpen() const { return internal != nullptr; } String NamedPipe::getName() const { return currentPipeName; } // other methods for this class are implemented in the platform-specific files END_JUCE_NAMESPACE /*** End of inlined file: juce_NamedPipe.cpp ***/ /*** Start of inlined file: juce_TemporaryFile.cpp ***/ BEGIN_JUCE_NAMESPACE TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) { createTempFile (File::getSpecialLocation (File::tempDirectory), "temp_" + String (Random::getSystemRandom().nextInt()), suffix, optionFlags); } TemporaryFile::TemporaryFile (const File& targetFile_, const int optionFlags) : targetFile (targetFile_) { // If you use this constructor, you need to give it a valid target file! jassert (targetFile != File::nonexistent); createTempFile (targetFile.getParentDirectory(), targetFile.getFileNameWithoutExtension() + "_temp" + String (Random::getSystemRandom().nextInt()), targetFile.getFileExtension(), optionFlags); } void TemporaryFile::createTempFile (const File& parentDirectory, String name, const String& suffix, const int optionFlags) { if ((optionFlags & useHiddenFile) != 0) name = "." + name; temporaryFile = parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & putNumbersInBrackets) != 0); } TemporaryFile::~TemporaryFile() { if (! deleteTemporaryFile()) { /* Failed to delete our temporary file! The most likely reason for this would be that you've not closed an output stream that was being used to write to file. If you find that something beyond your control is changing permissions on your temporary files and preventing them from being deleted, you may want to call TemporaryFile::deleteTemporaryFile() to detect those error cases and handle them appropriately. */ jassertfalse; } } bool TemporaryFile::overwriteTargetFileWithTemporary() const { // This method only works if you created this object with the constructor // that takes a target file! jassert (targetFile != File::nonexistent); if (temporaryFile.exists()) { // Have a few attempts at overwriting the file before giving up.. for (int i = 5; --i >= 0;) { if (temporaryFile.moveFileTo (targetFile)) return true; Thread::sleep (100); } } else { // There's no temporary file to use. If your write failed, you should // probably check, and not bother calling this method. jassertfalse; } return false; } bool TemporaryFile::deleteTemporaryFile() const { // Have a few attempts at deleting the file before giving up.. for (int i = 5; --i >= 0;) { if (temporaryFile.deleteFile()) return true; Thread::sleep (50); } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_TemporaryFile.cpp ***/ /*** Start of inlined file: juce_Socket.cpp ***/ #if JUCE_WINDOWS #include #include #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4127 4389 4018) #endif #else #if JUCE_LINUX || JUCE_ANDROID #include #include #include #include #include #endif #include #include #include #include #endif #ifndef AI_NUMERICSERV // (missing in older Mac SDKs) #define AI_NUMERICSERV 0x1000 #endif BEGIN_JUCE_NAMESPACE #if JUCE_WINDOWS typedef int juce_socklen_t; #else typedef socklen_t juce_socklen_t; #endif namespace SocketHelpers { void initSockets() { #if JUCE_WINDOWS static bool socketsStarted = false; if (! socketsStarted) { socketsStarted = true; WSADATA wsaData; const WORD wVersionRequested = MAKEWORD (1, 1); WSAStartup (wVersionRequested, &wsaData); } #endif } bool resetSocketOptions (const int handle, const bool isDatagram, const bool allowBroadcast) noexcept { const int sndBufSize = 65536; const int rcvBufSize = 65536; const int one = 1; return handle > 0 && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0 && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*) &sndBufSize, sizeof (sndBufSize)) == 0 && (isDatagram ? ((! allowBroadcast) || setsockopt (handle, SOL_SOCKET, SO_BROADCAST, (const char*) &one, sizeof (one)) == 0) : (setsockopt (handle, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof (one)) == 0)); } bool bindSocketToPort (const int handle, const int port) noexcept { if (handle <= 0 || port <= 0) return false; struct sockaddr_in servTmpAddr = { 0 }; servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); servTmpAddr.sin_port = htons ((uint16) port); return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; } int readSocket (const int handle, void* const destBuffer, const int maxBytesToRead, bool volatile& connected, const bool blockUntilSpecifiedAmountHasArrived) noexcept { int bytesRead = 0; while (bytesRead < maxBytesToRead) { int bytesThisTime; #if JUCE_WINDOWS bytesThisTime = recv (handle, static_cast (destBuffer) + bytesRead, maxBytesToRead - bytesRead, 0); #else while ((bytesThisTime = (int) ::read (handle, addBytesToPointer (destBuffer, bytesRead), maxBytesToRead - bytesRead)) < 0 && errno == EINTR && connected) { } #endif if (bytesThisTime <= 0 || ! connected) { if (bytesRead == 0) bytesRead = -1; break; } bytesRead += bytesThisTime; if (! blockUntilSpecifiedAmountHasArrived) break; } return bytesRead; } int waitForReadiness (const int handle, const bool forReading, const int timeoutMsecs) noexcept { struct timeval timeout; struct timeval* timeoutp; if (timeoutMsecs >= 0) { timeout.tv_sec = timeoutMsecs / 1000; timeout.tv_usec = (timeoutMsecs % 1000) * 1000; timeoutp = &timeout; } else { timeoutp = 0; } fd_set rset, wset; FD_ZERO (&rset); FD_SET (handle, &rset); FD_ZERO (&wset); FD_SET (handle, &wset); fd_set* const prset = forReading ? &rset : nullptr; fd_set* const pwset = forReading ? nullptr : &wset; #if JUCE_WINDOWS if (select (handle + 1, prset, pwset, 0, timeoutp) < 0) return -1; #else { int result; while ((result = select (handle + 1, prset, pwset, 0, timeoutp)) < 0 && errno == EINTR) { } if (result < 0) return -1; } #endif { int opt; juce_socklen_t len = sizeof (opt); if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 || opt != 0) return -1; } return FD_ISSET (handle, forReading ? &rset : &wset) ? 1 : 0; } bool setSocketBlockingState (const int handle, const bool shouldBlock) noexcept { #if JUCE_WINDOWS u_long nonBlocking = shouldBlock ? 0 : 1; return ioctlsocket (handle, FIONBIO, &nonBlocking) == 0; #else int socketFlags = fcntl (handle, F_GETFL, 0); if (socketFlags == -1) return false; if (shouldBlock) socketFlags &= ~O_NONBLOCK; else socketFlags |= O_NONBLOCK; return fcntl (handle, F_SETFL, socketFlags) == 0; #endif } bool connectSocket (int volatile& handle, const bool isDatagram, void** serverAddress, const String& hostName, const int portNumber, const int timeOutMillisecs) noexcept { struct addrinfo hints = { 0 }; hints.ai_family = AF_UNSPEC; hints.ai_socktype = isDatagram ? SOCK_DGRAM : SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; struct addrinfo* info = nullptr; if (getaddrinfo (hostName.toUTF8(), String (portNumber).toUTF8(), &hints, &info) != 0 || info == 0) return false; if (handle < 0) handle = (int) socket (info->ai_family, info->ai_socktype, 0); if (handle < 0) { freeaddrinfo (info); return false; } if (isDatagram) { struct sockaddr* s = new struct sockaddr(); *s = *(info->ai_addr); *serverAddress = s; freeaddrinfo (info); return true; } setSocketBlockingState (handle, false); const int result = ::connect (handle, info->ai_addr, info->ai_addrlen); freeaddrinfo (info); if (result < 0) { #if JUCE_WINDOWS if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) #else if (errno == EINPROGRESS) #endif { if (waitForReadiness (handle, false, timeOutMillisecs) != 1) { setSocketBlockingState (handle, true); return false; } } } setSocketBlockingState (handle, true); resetSocketOptions (handle, false, false); return true; } } StreamingSocket::StreamingSocket() : portNumber (0), handle (-1), connected (false), isListener (false) { SocketHelpers::initSockets(); } StreamingSocket::StreamingSocket (const String& hostName_, const int portNumber_, const int handle_) : hostName (hostName_), portNumber (portNumber_), handle (handle_), connected (true), isListener (false) { SocketHelpers::initSockets(); SocketHelpers::resetSocketOptions (handle_, false, false); } StreamingSocket::~StreamingSocket() { close(); } int StreamingSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) { return (connected && ! isListener) ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) : -1; } int StreamingSocket::write (const void* sourceBuffer, const int numBytesToWrite) { if (isListener || ! connected) return -1; #if JUCE_WINDOWS return send (handle, (const char*) sourceBuffer, numBytesToWrite, 0); #else int result; while ((result = (int) ::write (handle, sourceBuffer, numBytesToWrite)) < 0 && errno == EINTR) { } return result; #endif } int StreamingSocket::waitUntilReady (const bool readyForReading, const int timeoutMsecs) const { return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) : -1; } bool StreamingSocket::bindToPort (const int port) { return SocketHelpers::bindSocketToPort (handle, port); } bool StreamingSocket::connect (const String& remoteHostName, const int remotePortNumber, const int timeOutMillisecs) { if (isListener) { jassertfalse; // a listener socket can't connect to another one! return false; } if (connected) close(); hostName = remoteHostName; portNumber = remotePortNumber; isListener = false; connected = SocketHelpers::connectSocket (handle, false, 0, remoteHostName, remotePortNumber, timeOutMillisecs); if (! (connected && SocketHelpers::resetSocketOptions (handle, false, false))) { close(); return false; } return true; } void StreamingSocket::close() { #if JUCE_WINDOWS if (handle != SOCKET_ERROR || connected) closesocket (handle); connected = false; #else if (connected) { connected = false; if (isListener) { // need to do this to interrupt the accept() function.. StreamingSocket temp; temp.connect ("localhost", portNumber, 1000); } } if (handle != -1) ::close (handle); #endif hostName = String::empty; portNumber = 0; handle = -1; isListener = false; } bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) { if (connected) close(); hostName = "listener"; portNumber = newPortNumber; isListener = true; struct sockaddr_in servTmpAddr = { 0 }; servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); if (localHostName.isNotEmpty()) servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); servTmpAddr.sin_port = htons ((uint16) portNumber); handle = (int) socket (AF_INET, SOCK_STREAM, 0); if (handle < 0) return false; const int reuse = 1; setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 || listen (handle, SOMAXCONN) < 0) { close(); return false; } connected = true; return true; } StreamingSocket* StreamingSocket::waitForNextConnection() const { jassert (isListener || ! connected); // to call this method, you first have to use createListener() to // prepare this socket as a listener. if (connected && isListener) { struct sockaddr address; juce_socklen_t len = sizeof (sockaddr); const int newSocket = (int) accept (handle, &address, &len); if (newSocket >= 0 && connected) return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), portNumber, newSocket); } return nullptr; } bool StreamingSocket::isLocal() const noexcept { return hostName == "127.0.0.1"; } DatagramSocket::DatagramSocket (const int localPortNumber, const bool allowBroadcast_) : portNumber (0), handle (-1), connected (true), allowBroadcast (allowBroadcast_), serverAddress (0) { SocketHelpers::initSockets(); handle = (int) socket (AF_INET, SOCK_DGRAM, 0); bindToPort (localPortNumber); } DatagramSocket::DatagramSocket (const String& hostName_, const int portNumber_, const int handle_, const int localPortNumber) : hostName (hostName_), portNumber (portNumber_), handle (handle_), connected (true), allowBroadcast (false), serverAddress (0) { SocketHelpers::initSockets(); SocketHelpers::resetSocketOptions (handle_, true, allowBroadcast); bindToPort (localPortNumber); } DatagramSocket::~DatagramSocket() { close(); delete static_cast (serverAddress); serverAddress = 0; } void DatagramSocket::close() { #if JUCE_WINDOWS closesocket (handle); connected = false; #else connected = false; ::close (handle); #endif hostName = String::empty; portNumber = 0; handle = -1; } bool DatagramSocket::bindToPort (const int port) { return SocketHelpers::bindSocketToPort (handle, port); } bool DatagramSocket::connect (const String& remoteHostName, const int remotePortNumber, const int timeOutMillisecs) { if (connected) close(); hostName = remoteHostName; portNumber = remotePortNumber; connected = SocketHelpers::connectSocket (handle, true, &serverAddress, remoteHostName, remotePortNumber, timeOutMillisecs); if (! (connected && SocketHelpers::resetSocketOptions (handle, true, allowBroadcast))) { close(); return false; } return true; } DatagramSocket* DatagramSocket::waitForNextConnection() const { struct sockaddr address; juce_socklen_t len = sizeof (sockaddr); while (waitUntilReady (true, -1) == 1) { char buf[1]; if (recvfrom (handle, buf, 0, 0, &address, &len) > 0) { return new DatagramSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), ntohs (((struct sockaddr_in*) &address)->sin_port), -1, -1); } } return nullptr; } int DatagramSocket::waitUntilReady (const bool readyForReading, const int timeoutMsecs) const { return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) : -1; } int DatagramSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) { return connected ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) : -1; } int DatagramSocket::write (const void* sourceBuffer, const int numBytesToWrite) { // You need to call connect() first to set the server address.. jassert (serverAddress != 0 && connected); return connected ? (int) sendto (handle, (const char*) sourceBuffer, numBytesToWrite, 0, (const struct sockaddr*) serverAddress, sizeof (struct sockaddr_in)) : -1; } bool DatagramSocket::isLocal() const noexcept { return hostName == "127.0.0.1"; } #if JUCE_MSVC #pragma warning (pop) #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Socket.cpp ***/ /*** Start of inlined file: juce_URL.cpp ***/ BEGIN_JUCE_NAMESPACE URL::URL() { } URL::URL (const String& url_) : url (url_) { int i = url.indexOfChar ('?'); if (i >= 0) { do { const int nextAmp = url.indexOfChar (i + 1, '&'); const int equalsPos = url.indexOfChar (i + 1, '='); if (equalsPos > i + 1) { if (nextAmp < 0) { parameters.set (removeEscapeChars (url.substring (i + 1, equalsPos)), removeEscapeChars (url.substring (equalsPos + 1))); } else if (nextAmp > 0 && equalsPos < nextAmp) { parameters.set (removeEscapeChars (url.substring (i + 1, equalsPos)), removeEscapeChars (url.substring (equalsPos + 1, nextAmp))); } } i = nextAmp; } while (i >= 0); url = url.upToFirstOccurrenceOf ("?", false, false); } } URL::URL (const URL& other) : url (other.url), postData (other.postData), parameters (other.parameters), filesToUpload (other.filesToUpload), mimeTypes (other.mimeTypes) { } URL& URL::operator= (const URL& other) { url = other.url; postData = other.postData; parameters = other.parameters; filesToUpload = other.filesToUpload; mimeTypes = other.mimeTypes; return *this; } URL::~URL() { } namespace URLHelpers { String getMangledParameters (const StringPairArray& parameters) { String p; for (int i = 0; i < parameters.size(); ++i) { if (i > 0) p << '&'; p << URL::addEscapeChars (parameters.getAllKeys() [i], true) << '=' << URL::addEscapeChars (parameters.getAllValues() [i], true); } return p; } int findStartOfDomain (const String& url) { int i = 0; while (CharacterFunctions::isLetterOrDigit (url[i]) || url[i] == '+' || url[i] == '-' || url[i] == '.') ++i; return url[i] == ':' ? i + 1 : 0; } void createHeadersAndPostData (const URL& url, String& headers, MemoryBlock& postData) { MemoryOutputStream data (postData, false); if (url.getFilesToUpload().size() > 0) { // need to upload some files, so do it as multi-part... const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; data << "--" << boundary; int i; for (i = 0; i < url.getParameters().size(); ++i) { data << "\r\nContent-Disposition: form-data; name=\"" << url.getParameters().getAllKeys() [i] << "\"\r\n\r\n" << url.getParameters().getAllValues() [i] << "\r\n--" << boundary; } for (i = 0; i < url.getFilesToUpload().size(); ++i) { const File file (url.getFilesToUpload().getAllValues() [i]); const String paramName (url.getFilesToUpload().getAllKeys() [i]); data << "\r\nContent-Disposition: form-data; name=\"" << paramName << "\"; filename=\"" << file.getFileName() << "\"\r\n"; const String mimeType (url.getMimeTypesOfUploadFiles() .getValue (paramName, String::empty)); if (mimeType.isNotEmpty()) data << "Content-Type: " << mimeType << "\r\n"; data << "Content-Transfer-Encoding: binary\r\n\r\n" << file << "\r\n--" << boundary; } data << "--\r\n"; data.flush(); } else { data << getMangledParameters (url.getParameters()) << url.getPostData(); data.flush(); // just a short text attachment, so use simple url encoding.. headers << "Content-Type: application/x-www-form-urlencoded\r\nContent-length: " << (int) postData.getSize() << "\r\n"; } } } String URL::toString (const bool includeGetParameters) const { if (includeGetParameters && parameters.size() > 0) return url + "?" + URLHelpers::getMangledParameters (parameters); else return url; } bool URL::isWellFormed() const { //xxx TODO return url.isNotEmpty(); } String URL::getDomain() const { int start = URLHelpers::findStartOfDomain (url); while (url[start] == '/') ++start; const int end1 = url.indexOfChar (start, '/'); const int end2 = url.indexOfChar (start, ':'); const int end = (end1 < 0 || end2 < 0) ? jmax (end1, end2) : jmin (end1, end2); return url.substring (start, end); } String URL::getSubPath() const { int start = URLHelpers::findStartOfDomain (url); while (url[start] == '/') ++start; const int startOfPath = url.indexOfChar (start, '/') + 1; return startOfPath <= 0 ? String::empty : url.substring (startOfPath); } String URL::getScheme() const { return url.substring (0, URLHelpers::findStartOfDomain (url) - 1); } const URL URL::withNewSubPath (const String& newPath) const { int start = URLHelpers::findStartOfDomain (url); while (url[start] == '/') ++start; const int startOfPath = url.indexOfChar (start, '/') + 1; URL u (*this); if (startOfPath > 0) u.url = url.substring (0, startOfPath); if (! u.url.endsWithChar ('/')) u.url << '/'; if (newPath.startsWithChar ('/')) u.url << newPath.substring (1); else u.url << newPath; return u; } bool URL::isProbablyAWebsiteURL (const String& possibleURL) { const char* validProtocols[] = { "http:", "ftp:", "https:" }; for (int i = 0; i < numElementsInArray (validProtocols); ++i) if (possibleURL.startsWithIgnoreCase (validProtocols[i])) return true; if (possibleURL.containsChar ('@') || possibleURL.containsChar (' ')) return false; const String topLevelDomain (possibleURL.upToFirstOccurrenceOf ("/", false, false) .fromLastOccurrenceOf (".", false, false)); return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3; } bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress) { const int atSign = possibleEmailAddress.indexOfChar ('@'); return atSign > 0 && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1) && (! possibleEmailAddress.endsWithChar ('.')); } InputStream* URL::createInputStream (const bool usePostCommand, OpenStreamProgressCallback* const progressCallback, void* const progressCallbackContext, const String& extraHeaders, const int timeOutMs, StringPairArray* const responseHeaders) const { String headers; MemoryBlock headersAndPostData; if (usePostCommand) URLHelpers::createHeadersAndPostData (*this, headers, headersAndPostData); headers += extraHeaders; if (! headers.endsWithChar ('\n')) headers << "\r\n"; return createNativeStream (toString (! usePostCommand), usePostCommand, headersAndPostData, progressCallback, progressCallbackContext, headers, timeOutMs, responseHeaders); } bool URL::readEntireBinaryStream (MemoryBlock& destData, const bool usePostCommand) const { const ScopedPointer in (createInputStream (usePostCommand)); if (in != nullptr) { in->readIntoMemoryBlock (destData); return true; } return false; } String URL::readEntireTextStream (const bool usePostCommand) const { const ScopedPointer in (createInputStream (usePostCommand)); if (in != nullptr) return in->readEntireStreamAsString(); return String::empty; } XmlElement* URL::readEntireXmlStream (const bool usePostCommand) const { return XmlDocument::parse (readEntireTextStream (usePostCommand)); } const URL URL::withParameter (const String& parameterName, const String& parameterValue) const { URL u (*this); u.parameters.set (parameterName, parameterValue); return u; } const URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload, const String& mimeType) const { jassert (mimeType.isNotEmpty()); // You need to supply a mime type! URL u (*this); u.filesToUpload.set (parameterName, fileToUpload.getFullPathName()); u.mimeTypes.set (parameterName, mimeType); return u; } const URL URL::withPOSTData (const String& postData_) const { URL u (*this); u.postData = postData_; return u; } const StringPairArray& URL::getParameters() const { return parameters; } const StringPairArray& URL::getFilesToUpload() const { return filesToUpload; } const StringPairArray& URL::getMimeTypesOfUploadFiles() const { return mimeTypes; } String URL::removeEscapeChars (const String& s) { String result (s.replaceCharacter ('+', ' ')); if (! result.containsChar ('%')) return result; // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode // after all the replacements have been made, so that multi-byte chars are handled. Array utf8 (result.toUTF8().getAddress(), result.getNumBytesAsUTF8()); for (int i = 0; i < utf8.size(); ++i) { if (utf8.getUnchecked(i) == '%') { const int hexDigit1 = CharacterFunctions::getHexDigitValue (utf8 [i + 1]); const int hexDigit2 = CharacterFunctions::getHexDigitValue (utf8 [i + 2]); if (hexDigit1 >= 0 && hexDigit2 >= 0) { utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2)); utf8.removeRange (i + 1, 2); } } } return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); } String URL::addEscapeChars (const String& s, const bool isParameter) { const CharPointer_UTF8 legalChars (isParameter ? "_-.*!'()" : ",$_-.*!'()"); Array utf8 (s.toUTF8().getAddress(), s.getNumBytesAsUTF8()); for (int i = 0; i < utf8.size(); ++i) { const char c = utf8.getUnchecked(i); if (! (CharacterFunctions::isLetterOrDigit (c) || legalChars.indexOf ((juce_wchar) c) >= 0)) { if (c == ' ') { utf8.set (i, '+'); } else { static const char* const hexDigits = "0123456789abcdef"; utf8.set (i, '%'); utf8.insert (++i, hexDigits [((uint8) c) >> 4]); utf8.insert (++i, hexDigits [c & 15]); } } } return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); } bool URL::launchInDefaultBrowser() const { String u (toString (true)); if (u.containsChar ('@') && ! u.containsChar (':')) u = "mailto:" + u; return PlatformUtilities::openDocument (u, String::empty); } END_JUCE_NAMESPACE /*** End of inlined file: juce_URL.cpp ***/ /*** Start of inlined file: juce_MACAddress.cpp ***/ BEGIN_JUCE_NAMESPACE MACAddress::MACAddress() : asInt64 (0) { } MACAddress::MACAddress (const MACAddress& other) : asInt64 (other.asInt64) { } MACAddress& MACAddress::operator= (const MACAddress& other) { asInt64 = other.asInt64; return *this; } MACAddress::MACAddress (const uint8 bytes[6]) : asInt64 (0) { memcpy (asBytes, bytes, sizeof (asBytes)); } const String MACAddress::toString() const { String s; for (int i = 0; i < numElementsInArray (asBytes); ++i) { s << String::toHexString ((int) asBytes[i]).paddedLeft ('0', 2); if (i < numElementsInArray (asBytes) - 1) s << '-'; } return s; } int64 MACAddress::toInt64() const noexcept { int64 n = 0; for (int i = numElementsInArray (asBytes); --i >= 0;) n = (n << 8) | asBytes[i]; return n; } bool MACAddress::isNull() const noexcept { return asInt64 == 0; } bool MACAddress::operator== (const MACAddress& other) const noexcept { return asInt64 == other.asInt64; } bool MACAddress::operator!= (const MACAddress& other) const noexcept { return asInt64 != other.asInt64; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MACAddress.cpp ***/ /*** Start of inlined file: juce_BufferedInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE namespace { int calcBufferStreamBufferSize (int requestedSize, InputStream* const source) noexcept { // You need to supply a real stream when creating a BufferedInputStream jassert (source != nullptr); requestedSize = jmax (256, requestedSize); const int64 sourceSize = source->getTotalLength(); if (sourceSize >= 0 && sourceSize < requestedSize) requestedSize = jmax (32, (int) sourceSize); return requestedSize; } } BufferedInputStream::BufferedInputStream (InputStream* const sourceStream, const int bufferSize_, const bool deleteSourceWhenDestroyed) : source (sourceStream, deleteSourceWhenDestroyed), bufferSize (calcBufferStreamBufferSize (bufferSize_, sourceStream)), position (sourceStream->getPosition()), lastReadPos (0), bufferStart (position), bufferOverlap (128) { buffer.malloc (bufferSize); } BufferedInputStream::BufferedInputStream (InputStream& sourceStream, const int bufferSize_) : source (&sourceStream, false), bufferSize (calcBufferStreamBufferSize (bufferSize_, &sourceStream)), position (sourceStream.getPosition()), lastReadPos (0), bufferStart (position), bufferOverlap (128) { buffer.malloc (bufferSize); } BufferedInputStream::~BufferedInputStream() { } int64 BufferedInputStream::getTotalLength() { return source->getTotalLength(); } int64 BufferedInputStream::getPosition() { return position; } bool BufferedInputStream::setPosition (int64 newPosition) { position = jmax ((int64) 0, newPosition); return true; } bool BufferedInputStream::isExhausted() { return (position >= lastReadPos) && source->isExhausted(); } void BufferedInputStream::ensureBuffered() { const int64 bufferEndOverlap = lastReadPos - bufferOverlap; if (position < bufferStart || position >= bufferEndOverlap) { int bytesRead; if (position < lastReadPos && position >= bufferEndOverlap && position >= bufferStart) { const int bytesToKeep = (int) (lastReadPos - position); memmove (buffer, buffer + (int) (position - bufferStart), bytesToKeep); bufferStart = position; bytesRead = source->read (buffer + bytesToKeep, bufferSize - bytesToKeep); lastReadPos += bytesRead; bytesRead += bytesToKeep; } else { bufferStart = position; source->setPosition (bufferStart); bytesRead = source->read (buffer, bufferSize); lastReadPos = bufferStart + bytesRead; } while (bytesRead < bufferSize) buffer [bytesRead++] = 0; } } int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) { if (position >= bufferStart && position + maxBytesToRead <= lastReadPos) { memcpy (destBuffer, buffer + (int) (position - bufferStart), maxBytesToRead); position += maxBytesToRead; return maxBytesToRead; } else { if (position < bufferStart || position >= lastReadPos) ensureBuffered(); int bytesRead = 0; while (maxBytesToRead > 0) { const int bytesAvailable = jmin (maxBytesToRead, (int) (lastReadPos - position)); if (bytesAvailable > 0) { memcpy (destBuffer, buffer + (int) (position - bufferStart), bytesAvailable); maxBytesToRead -= bytesAvailable; bytesRead += bytesAvailable; position += bytesAvailable; destBuffer = static_cast (destBuffer) + bytesAvailable; } const int64 oldLastReadPos = lastReadPos; ensureBuffered(); if (oldLastReadPos == lastReadPos) break; // if ensureBuffered() failed to read any more data, bail out if (isExhausted()) break; } return bytesRead; } } String BufferedInputStream::readString() { if (position >= bufferStart && position < lastReadPos) { const int maxChars = (int) (lastReadPos - position); const char* const src = buffer + (int) (position - bufferStart); for (int i = 0; i < maxChars; ++i) { if (src[i] == 0) { position += i + 1; return String::fromUTF8 (src, i); } } } return InputStream::readString(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BufferedInputStream.cpp ***/ /*** Start of inlined file: juce_FileInputSource.cpp ***/ BEGIN_JUCE_NAMESPACE FileInputSource::FileInputSource (const File& file_, bool useFileTimeInHashGeneration_) : file (file_), useFileTimeInHashGeneration (useFileTimeInHashGeneration_) { } FileInputSource::~FileInputSource() { } InputStream* FileInputSource::createInputStream() { return file.createInputStream(); } InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) { return file.getSiblingFile (relatedItemPath).createInputStream(); } int64 FileInputSource::hashCode() const { int64 h = file.hashCode(); if (useFileTimeInHashGeneration) h ^= file.getLastModificationTime().toMilliseconds(); return h; } END_JUCE_NAMESPACE /*** End of inlined file: juce_FileInputSource.cpp ***/ /*** Start of inlined file: juce_MemoryInputStream.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryInputStream::MemoryInputStream (const void* const sourceData, const size_t sourceDataSize, const bool keepInternalCopy) : data (static_cast (sourceData)), dataSize (sourceDataSize), position (0) { if (keepInternalCopy) createInternalCopy(); } MemoryInputStream::MemoryInputStream (const MemoryBlock& sourceData, const bool keepInternalCopy) : data (static_cast (sourceData.getData())), dataSize (sourceData.getSize()), position (0) { if (keepInternalCopy) createInternalCopy(); } void MemoryInputStream::createInternalCopy() { internalCopy.malloc (dataSize); memcpy (internalCopy, data, dataSize); data = internalCopy; } MemoryInputStream::~MemoryInputStream() { } int64 MemoryInputStream::getTotalLength() { return dataSize; } int MemoryInputStream::read (void* const buffer, const int howMany) { jassert (howMany >= 0); const int num = jmin (howMany, (int) (dataSize - position)); if (num <= 0) return 0; memcpy (buffer, data + position, num); position += num; return (int) num; } bool MemoryInputStream::isExhausted() { return position >= dataSize; } bool MemoryInputStream::setPosition (const int64 pos) { position = (int) jlimit ((int64) 0, (int64) dataSize, pos); return true; } int64 MemoryInputStream::getPosition() { return position; } #if JUCE_UNIT_TESTS class MemoryStreamTests : public UnitTest { public: MemoryStreamTests() : UnitTest ("MemoryInputStream & MemoryOutputStream") {} void runTest() { beginTest ("Basics"); int randomInt = Random::getSystemRandom().nextInt(); int64 randomInt64 = Random::getSystemRandom().nextInt64(); double randomDouble = Random::getSystemRandom().nextDouble(); String randomString (createRandomWideCharString()); MemoryOutputStream mo; mo.writeInt (randomInt); mo.writeIntBigEndian (randomInt); mo.writeCompressedInt (randomInt); mo.writeString (randomString); mo.writeInt64 (randomInt64); mo.writeInt64BigEndian (randomInt64); mo.writeDouble (randomDouble); mo.writeDoubleBigEndian (randomDouble); MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); expect (mi.readInt() == randomInt); expect (mi.readIntBigEndian() == randomInt); expect (mi.readCompressedInt() == randomInt); expectEquals (mi.readString(), randomString); expect (mi.readInt64() == randomInt64); expect (mi.readInt64BigEndian() == randomInt64); expect (mi.readDouble() == randomDouble); expect (mi.readDoubleBigEndian() == randomDouble); } static const String createRandomWideCharString() { juce_wchar buffer [50] = { 0 }; for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) { if (Random::getSystemRandom().nextBool()) { do { buffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (0x10ffff - 1)); } while (! CharPointer_UTF16::canRepresent (buffer[i])); } else buffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (0xff)); } return CharPointer_UTF32 (buffer); } }; static MemoryStreamTests memoryInputStreamUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryInputStream.cpp ***/ /*** Start of inlined file: juce_MemoryOutputStream.cpp ***/ BEGIN_JUCE_NAMESPACE MemoryOutputStream::MemoryOutputStream (const size_t initialSize) : data (internalBlock), position (0), size (0) { internalBlock.setSize (initialSize, false); } MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, const bool appendToExistingBlockContent) : data (memoryBlockToWriteTo), position (0), size (0) { if (appendToExistingBlockContent) position = size = memoryBlockToWriteTo.getSize(); } MemoryOutputStream::~MemoryOutputStream() { trimExternalBlockSize(); } void MemoryOutputStream::flush() { trimExternalBlockSize(); } void MemoryOutputStream::trimExternalBlockSize() { if (&data != &internalBlock) data.setSize (size, false); } void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) { data.ensureSize (bytesToPreallocate + 1); } void MemoryOutputStream::reset() noexcept { position = 0; size = 0; } void MemoryOutputStream::prepareToWrite (int numBytes) { const size_t storageNeeded = position + numBytes; if (storageNeeded >= data.getSize()) data.ensureSize ((storageNeeded + jmin ((int) (storageNeeded / 2), 1024 * 1024) + 32) & ~31); } bool MemoryOutputStream::write (const void* const buffer, int howMany) { if (howMany > 0) { prepareToWrite (howMany); memcpy (static_cast (data.getData()) + position, buffer, howMany); position += howMany; size = jmax (size, position); } return true; } void MemoryOutputStream::writeRepeatedByte (uint8 byte, int howMany) { if (howMany > 0) { prepareToWrite (howMany); memset (static_cast (data.getData()) + position, byte, howMany); position += howMany; size = jmax (size, position); } } const void* MemoryOutputStream::getData() const noexcept { void* const d = data.getData(); if (data.getSize() > size) static_cast (d) [size] = 0; return d; } bool MemoryOutputStream::setPosition (int64 newPosition) { if (newPosition <= (int64) size) { // ok to seek backwards position = jlimit ((size_t) 0, size, (size_t) newPosition); return true; } else { // trying to make it bigger isn't a good thing to do.. return false; } } int MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite) { // before writing from an input, see if we can preallocate to make it more efficient.. int64 availableData = source.getTotalLength() - source.getPosition(); if (availableData > 0) { if (maxNumBytesToWrite > 0 && maxNumBytesToWrite < availableData) availableData = maxNumBytesToWrite; preallocate (data.getSize() + (size_t) maxNumBytesToWrite); } return OutputStream::writeFromInputStream (source, maxNumBytesToWrite); } String MemoryOutputStream::toUTF8() const { const char* const d = static_cast (getData()); return String (CharPointer_UTF8 (d), CharPointer_UTF8 (d + getDataSize())); } String MemoryOutputStream::toString() const { return String::createStringFromData (getData(), (int) getDataSize()); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead) { stream.write (streamToRead.getData(), (int) streamToRead.getDataSize()); return stream; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MemoryOutputStream.cpp ***/ /*** Start of inlined file: juce_SubregionStream.cpp ***/ BEGIN_JUCE_NAMESPACE SubregionStream::SubregionStream (InputStream* const sourceStream, const int64 startPositionInSourceStream_, const int64 lengthOfSourceStream_, const bool deleteSourceWhenDestroyed) : source (sourceStream, deleteSourceWhenDestroyed), startPositionInSourceStream (startPositionInSourceStream_), lengthOfSourceStream (lengthOfSourceStream_) { SubregionStream::setPosition (0); } SubregionStream::~SubregionStream() { } int64 SubregionStream::getTotalLength() { const int64 srcLen = source->getTotalLength() - startPositionInSourceStream; return (lengthOfSourceStream >= 0) ? jmin (lengthOfSourceStream, srcLen) : srcLen; } int64 SubregionStream::getPosition() { return source->getPosition() - startPositionInSourceStream; } bool SubregionStream::setPosition (int64 newPosition) { return source->setPosition (jmax ((int64) 0, newPosition + startPositionInSourceStream)); } int SubregionStream::read (void* destBuffer, int maxBytesToRead) { if (lengthOfSourceStream < 0) { return source->read (destBuffer, maxBytesToRead); } else { maxBytesToRead = (int) jmin ((int64) maxBytesToRead, lengthOfSourceStream - getPosition()); if (maxBytesToRead <= 0) return 0; return source->read (destBuffer, maxBytesToRead); } } bool SubregionStream::isExhausted() { if (lengthOfSourceStream >= 0) return (getPosition() >= lengthOfSourceStream) || source->isExhausted(); else return source->isExhausted(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_SubregionStream.cpp ***/ /*** Start of inlined file: juce_PerformanceCounter.cpp ***/ BEGIN_JUCE_NAMESPACE PerformanceCounter::PerformanceCounter (const String& name_, int runsPerPrintout, const File& loggingFile) : name (name_), numRuns (0), runsPerPrint (runsPerPrintout), totalTime (0), outputFile (loggingFile) { if (outputFile != File::nonexistent) { String s ("**** Counter for \""); s << name_ << "\" started at: " << Time::getCurrentTime().toString (true, true) << newLine; outputFile.appendText (s, false, false); } } PerformanceCounter::~PerformanceCounter() { printStatistics(); } void PerformanceCounter::start() { started = Time::getHighResolutionTicks(); } void PerformanceCounter::stop() { const int64 now = Time::getHighResolutionTicks(); totalTime += 1000.0 * Time::highResolutionTicksToSeconds (now - started); if (++numRuns == runsPerPrint) printStatistics(); } void PerformanceCounter::printStatistics() { if (numRuns > 0) { String s ("Performance count for \""); s << name << "\" - average over " << numRuns << " run(s) = "; const int micros = (int) (totalTime * (1000.0 / numRuns)); if (micros > 10000) s << (micros/1000) << " millisecs"; else s << micros << " microsecs"; s << ", total = " << String (totalTime / 1000, 5) << " seconds"; Logger::outputDebugString (s); s << newLine; if (outputFile != File::nonexistent) outputFile.appendText (s, false, false); numRuns = 0; totalTime = 0; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_PerformanceCounter.cpp ***/ /*** Start of inlined file: juce_Uuid.cpp ***/ BEGIN_JUCE_NAMESPACE Uuid::Uuid() { // Mix up any available MAC addresses with some time-based pseudo-random numbers // to make it very very unlikely that two UUIDs will ever be the same.. static int64 macAddresses[2]; static bool hasCheckedMacAddresses = false; if (! hasCheckedMacAddresses) { hasCheckedMacAddresses = true; Array result; MACAddress::findAllAddresses (result); for (int i = 0; i < numElementsInArray (macAddresses); ++i) macAddresses[i] = result[i].toInt64(); } value.asInt64[0] = macAddresses[0]; value.asInt64[1] = macAddresses[1]; // We'll use both a local RNG that is re-seeded, plus the shared RNG, // whose seed will carry over between calls to this method. Random r (macAddresses[0] ^ macAddresses[1] ^ Random::getSystemRandom().nextInt64()); for (int i = 4; --i >= 0;) { r.setSeedRandomly(); // calling this repeatedly improves randomness value.asInt[i] ^= r.nextInt(); value.asInt[i] ^= Random::getSystemRandom().nextInt(); } } Uuid::~Uuid() noexcept { } Uuid::Uuid (const Uuid& other) : value (other.value) { } Uuid& Uuid::operator= (const Uuid& other) { value = other.value; return *this; } bool Uuid::operator== (const Uuid& other) const { return value.asInt64[0] == other.value.asInt64[0] && value.asInt64[1] == other.value.asInt64[1]; } bool Uuid::operator!= (const Uuid& other) const { return ! operator== (other); } bool Uuid::isNull() const noexcept { return (value.asInt64 [0] == 0) && (value.asInt64 [1] == 0); } String Uuid::toString() const { return String::toHexString (value.asBytes, sizeof (value.asBytes), 0); } Uuid::Uuid (const String& uuidString) { operator= (uuidString); } Uuid& Uuid::operator= (const String& uuidString) { MemoryBlock mb; mb.loadFromHexString (uuidString); mb.ensureSize (sizeof (value.asBytes), true); mb.copyTo (value.asBytes, 0, sizeof (value.asBytes)); return *this; } Uuid::Uuid (const uint8* const rawData) { operator= (rawData); } Uuid& Uuid::operator= (const uint8* const rawData) { if (rawData != nullptr) memcpy (value.asBytes, rawData, sizeof (value.asBytes)); else zeromem (value.asBytes, sizeof (value.asBytes)); return *this; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Uuid.cpp ***/ /*** Start of inlined file: juce_ZipFile.cpp ***/ BEGIN_JUCE_NAMESPACE class ZipFile::ZipEntryInfo { public: ZipFile::ZipEntry entry; int streamOffset; int compressedSize; bool compressed; }; class ZipFile::ZipInputStream : public InputStream { public: ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei) : file (file_), zipEntryInfo (zei), pos (0), headerSize (0), inputStream (file_.inputStream) { if (file_.inputSource != nullptr) { inputStream = streamToDelete = file.inputSource->createInputStream(); } else { #if JUCE_DEBUG file_.numOpenStreams++; #endif } char buffer [30]; if (inputStream != nullptr && inputStream->setPosition (zei.streamOffset) && inputStream->read (buffer, 30) == 30 && ByteOrder::littleEndianInt (buffer) == 0x04034b50) { headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26) + ByteOrder::littleEndianShort (buffer + 28); } } ~ZipInputStream() { #if JUCE_DEBUG if (inputStream != nullptr && inputStream == file.inputStream) file.numOpenStreams--; #endif } int64 getTotalLength() { return zipEntryInfo.compressedSize; } int read (void* buffer, int howMany) { if (headerSize <= 0) return 0; howMany = (int) jmin ((int64) howMany, zipEntryInfo.compressedSize - pos); if (inputStream == nullptr) return 0; int num; if (inputStream == file.inputStream) { const ScopedLock sl (file.lock); inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } else { inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } pos += num; return num; } bool isExhausted() { return headerSize <= 0 || pos >= zipEntryInfo.compressedSize; } int64 getPosition() { return pos; } bool setPosition (int64 newPos) { pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos); return true; } private: ZipFile& file; ZipEntryInfo zipEntryInfo; int64 pos; int headerSize; InputStream* inputStream; ScopedPointer streamToDelete; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream); }; ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed) : inputStream (source_) #if JUCE_DEBUG , numOpenStreams (0) #endif { if (deleteStreamWhenDestroyed) streamToDelete = inputStream; init(); } ZipFile::ZipFile (const File& file) : inputStream (nullptr) #if JUCE_DEBUG , numOpenStreams (0) #endif { inputSource = new FileInputSource (file); init(); } ZipFile::ZipFile (InputSource* const inputSource_) : inputStream (nullptr), inputSource (inputSource_) #if JUCE_DEBUG , numOpenStreams (0) #endif { init(); } ZipFile::~ZipFile() { #if JUCE_DEBUG entries.clear(); /* If you hit this assertion, it means you've created a stream to read one of the items in the zipfile, but you've forgotten to delete that stream object before deleting the file.. Streams can't be kept open after the file is deleted because they need to share the input stream that the file uses to read itself. */ jassert (numOpenStreams == 0); #endif } int ZipFile::getNumEntries() const noexcept { return entries.size(); } const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept { ZipEntryInfo* const zei = entries [index]; return zei != nullptr ? &(zei->entry) : nullptr; } int ZipFile::getIndexOfFileName (const String& fileName) const noexcept { for (int i = 0; i < entries.size(); ++i) if (entries.getUnchecked (i)->entry.filename == fileName) return i; return -1; } const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept { return getEntry (getIndexOfFileName (fileName)); } InputStream* ZipFile::createStreamForEntry (const int index) { ZipEntryInfo* const zei = entries[index]; InputStream* stream = nullptr; if (zei != nullptr) { stream = new ZipInputStream (*this, *zei); if (zei->compressed) { stream = new GZIPDecompressorInputStream (stream, true, true, zei->entry.uncompressedSize); // (much faster to unzip in big blocks using a buffer..) stream = new BufferedInputStream (stream, 32768, true); } } return stream; } class ZipFile::ZipFilenameComparator { public: int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second) { return first->entry.filename.compare (second->entry.filename); } }; void ZipFile::sortEntriesByFilename() { ZipFilenameComparator sorter; entries.sort (sorter); } void ZipFile::init() { ScopedPointer toDelete; InputStream* in = inputStream; if (inputSource != nullptr) { in = inputSource->createInputStream(); toDelete = in; } if (in != nullptr) { int numEntries = 0; int pos = findEndOfZipEntryTable (*in, numEntries); if (pos >= 0 && pos < in->getTotalLength()) { const int size = (int) (in->getTotalLength() - pos); in->setPosition (pos); MemoryBlock headerData; if (in->readIntoMemoryBlock (headerData, size) == size) { pos = 0; for (int i = 0; i < numEntries; ++i) { if (pos + 46 > size) break; const char* const buffer = static_cast (headerData.getData()) + pos; const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); if (pos + 46 + fileNameLen > size) break; ZipEntryInfo* const zei = new ZipEntryInfo(); zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); const int time = ByteOrder::littleEndianShort (buffer + 12); const int date = ByteOrder::littleEndianShort (buffer + 14); const int year = 1980 + (date >> 9); const int month = ((date >> 5) & 15) - 1; const int day = date & 31; const int hours = time >> 11; const int minutes = (time >> 5) & 63; const int seconds = (time & 31) << 1; zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds); zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; zei->compressedSize = ByteOrder::littleEndianInt (buffer + 20); zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42); entries.add (zei); pos += 46 + fileNameLen + ByteOrder::littleEndianShort (buffer + 30) + ByteOrder::littleEndianShort (buffer + 32); } } } } } int ZipFile::findEndOfZipEntryTable (InputStream& input, int& numEntries) { BufferedInputStream in (input, 8192); in.setPosition (in.getTotalLength()); int64 pos = in.getPosition(); const int64 lowestPos = jmax ((int64) 0, pos - 1024); char buffer [32] = { 0 }; while (pos > lowestPos) { in.setPosition (pos - 22); pos = in.getPosition(); memcpy (buffer + 22, buffer, 4); if (in.read (buffer, 22) != 22) return 0; for (int i = 0; i < 22; ++i) { if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50) { in.setPosition (pos + i); in.read (buffer, 22); numEntries = ByteOrder::littleEndianShort (buffer + 10); return ByteOrder::littleEndianInt (buffer + 16); } } } return 0; } bool ZipFile::uncompressTo (const File& targetDirectory, const bool shouldOverwriteFiles) { for (int i = 0; i < entries.size(); ++i) if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles)) return false; return true; } bool ZipFile::uncompressEntry (const int index, const File& targetDirectory, bool shouldOverwriteFiles) { const ZipEntryInfo* zei = entries [index]; if (zei != nullptr) { const File targetFile (targetDirectory.getChildFile (zei->entry.filename)); if (zei->entry.filename.endsWithChar ('/')) { return targetFile.createDirectory(); // (entry is a directory, not a file) } else { ScopedPointer in (createStreamForEntry (index)); if (in != nullptr) { if (shouldOverwriteFiles && ! targetFile.deleteFile()) return false; if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory()) { ScopedPointer out (targetFile.createOutputStream()); if (out != nullptr) { out->writeFromInputStream (*in, -1); out = nullptr; targetFile.setCreationTime (zei->entry.fileTime); targetFile.setLastModificationTime (zei->entry.fileTime); targetFile.setLastAccessTime (zei->entry.fileTime); return true; } } } } } return false; } extern unsigned long juce_crc32 (unsigned long crc, const unsigned char* buf, unsigned len); class ZipFile::Builder::Item { public: Item (const File& file_, const int compressionLevel_, const String& storedPathName_) : file (file_), storedPathname (storedPathName_.isEmpty() ? file_.getFileName() : storedPathName_), compressionLevel (compressionLevel_), compressedSize (0), headerStart (0) { } bool writeData (OutputStream& target, const int64 overallStartPosition) { MemoryOutputStream compressedData; if (compressionLevel > 0) { GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false, GZIPCompressorOutputStream::windowBitsRaw); if (! writeSource (compressor)) return false; } else { if (! writeSource (compressedData)) return false; } compressedSize = compressedData.getDataSize(); headerStart = (int) (target.getPosition() - overallStartPosition); target.writeInt (0x04034b50); writeFlagsAndSizes (target); target << storedPathname << compressedData; return true; } bool writeDirectoryEntry (OutputStream& target) { target.writeInt (0x02014b50); target.writeShort (20); // version written writeFlagsAndSizes (target); target.writeShort (0); // comment length target.writeShort (0); // start disk num target.writeShort (0); // internal attributes target.writeInt (0); // external attributes target.writeInt (headerStart); target << storedPathname; return true; } private: const File file; String storedPathname; int compressionLevel, compressedSize, headerStart; uint32 checksum; void writeTimeAndDate (OutputStream& target) const { const Time t (file.getLastModificationTime()); target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11))); target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9))); } bool writeSource (OutputStream& target) { checksum = 0; ScopedPointer input (file.createInputStream()); if (input == nullptr) return false; const int bufferSize = 2048; HeapBlock buffer (bufferSize); while (! input->isExhausted()) { const int bytesRead = input->read (buffer, bufferSize); if (bytesRead < 0) return false; checksum = juce_crc32 (checksum, buffer, bytesRead); target.write (buffer, bytesRead); } return true; } void writeFlagsAndSizes (OutputStream& target) const { target.writeShort (10); // version needed target.writeShort (0); // flags target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0); writeTimeAndDate (target); target.writeInt (checksum); target.writeInt (compressedSize); target.writeInt ((int) file.getSize()); target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1); target.writeShort (0); // extra field length } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item); }; ZipFile::Builder::Builder() {} ZipFile::Builder::~Builder() {} void ZipFile::Builder::addFile (const File& fileToAdd, const int compressionLevel, const String& storedPathName) { items.add (new Item (fileToAdd, compressionLevel, storedPathName)); } bool ZipFile::Builder::writeToStream (OutputStream& target) const { const int64 fileStart = target.getPosition(); int i; for (i = 0; i < items.size(); ++i) if (! items.getUnchecked (i)->writeData (target, fileStart)) return false; const int64 directoryStart = target.getPosition(); for (i = 0; i < items.size(); ++i) if (! items.getUnchecked (i)->writeDirectoryEntry (target)) return false; const int64 directoryEnd = target.getPosition(); target.writeInt (0x06054b50); target.writeShort (0); target.writeShort (0); target.writeShort ((short) items.size()); target.writeShort ((short) items.size()); target.writeInt ((int) (directoryEnd - directoryStart)); target.writeInt ((int) (directoryStart - fileStart)); target.writeShort (0); target.flush(); return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ZipFile.cpp ***/ /*** Start of inlined file: juce_CharacterFunctions.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4996) #endif #if ! JUCE_ANDROID #include #endif #include BEGIN_JUCE_NAMESPACE juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) noexcept { return towupper ((wchar_t) character); } juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) noexcept { return towlower ((wchar_t) character); } bool CharacterFunctions::isUpperCase (const juce_wchar character) noexcept { #if JUCE_WINDOWS return iswupper ((wchar_t) character) != 0; #else return toLowerCase (character) != character; #endif } bool CharacterFunctions::isLowerCase (const juce_wchar character) noexcept { #if JUCE_WINDOWS return iswlower ((wchar_t) character) != 0; #else return toUpperCase (character) != character; #endif } #if JUCE_MSVC #pragma warning (pop) #endif bool CharacterFunctions::isWhitespace (const char character) noexcept { return character == ' ' || (character <= 13 && character >= 9); } bool CharacterFunctions::isWhitespace (const juce_wchar character) noexcept { return iswspace ((wchar_t) character) != 0; } bool CharacterFunctions::isDigit (const char character) noexcept { return (character >= '0' && character <= '9'); } bool CharacterFunctions::isDigit (const juce_wchar character) noexcept { return iswdigit ((wchar_t) character) != 0; } bool CharacterFunctions::isLetter (const char character) noexcept { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z'); } bool CharacterFunctions::isLetter (const juce_wchar character) noexcept { return iswalpha ((wchar_t) character) != 0; } bool CharacterFunctions::isLetterOrDigit (const char character) noexcept { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9'); } bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept { return iswalnum ((wchar_t) character) != 0; } int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept { unsigned int d = digit - '0'; if (d < (unsigned int) 10) return (int) d; d += (unsigned int) ('0' - 'a'); if (d < (unsigned int) 6) return (int) d + 10; d += (unsigned int) ('a' - 'A'); if (d < (unsigned int) 6) return (int) d + 10; return -1; } double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept { if (exponent == 0) return value; if (value == 0) return 0; const bool negative = (exponent < 0); if (negative) exponent = -exponent; double result = 1.0, power = 10.0; for (int bit = 1; exponent != 0; bit <<= 1) { if ((exponent & bit) != 0) { exponent ^= bit; result *= power; if (exponent == 0) break; } power *= power; } return negative ? (value / result) : (value * result); } END_JUCE_NAMESPACE /*** End of inlined file: juce_CharacterFunctions.cpp ***/ /*** Start of inlined file: juce_LocalisedStrings.cpp ***/ BEGIN_JUCE_NAMESPACE LocalisedStrings::LocalisedStrings (const String& fileContents) { loadFromText (fileContents); } LocalisedStrings::LocalisedStrings (const File& fileToLoad) { loadFromText (fileToLoad.loadFileAsString()); } LocalisedStrings::~LocalisedStrings() { } String LocalisedStrings::translate (const String& text) const { return translations.getValue (text, text); } namespace { #if JUCE_CHECK_MEMORY_LEAKS // By using this object to force a LocalisedStrings object to be created // before the currentMappings object, we can force the static order-of-destruction to // delete the currentMappings object first, which avoids a bogus leak warning. // (Oddly, just creating a LocalisedStrings on the stack doesn't work in gcc, it // has to be created with 'new' for this to work..) struct LeakAvoidanceTrick { LeakAvoidanceTrick() { const ScopedPointer dummy (new LocalisedStrings (String())); } }; LeakAvoidanceTrick leakAvoidanceTrick; #endif SpinLock currentMappingsLock; ScopedPointer currentMappings; int findCloseQuote (const String& text, int startPos) { juce_wchar lastChar = 0; String::CharPointerType t (text.getCharPointer() + startPos); for (;;) { const juce_wchar c = t.getAndAdvance(); if (c == 0 || (c == '"' && lastChar != '\\')) break; lastChar = c; ++startPos; } return startPos; } String unescapeString (const String& s) { return s.replace ("\\\"", "\"") .replace ("\\\'", "\'") .replace ("\\t", "\t") .replace ("\\r", "\r") .replace ("\\n", "\n"); } } void LocalisedStrings::loadFromText (const String& fileContents) { StringArray lines; lines.addLines (fileContents); for (int i = 0; i < lines.size(); ++i) { String line (lines[i].trim()); if (line.startsWithChar ('"')) { int closeQuote = findCloseQuote (line, 1); const String originalText (unescapeString (line.substring (1, closeQuote))); if (originalText.isNotEmpty()) { const int openingQuote = findCloseQuote (line, closeQuote + 1); closeQuote = findCloseQuote (line, openingQuote + 1); const String newText (unescapeString (line.substring (openingQuote + 1, closeQuote))); if (newText.isNotEmpty()) translations.set (originalText, newText); } } else if (line.startsWithIgnoreCase ("language:")) { languageName = line.substring (9).trim(); } else if (line.startsWithIgnoreCase ("countries:")) { countryCodes.addTokens (line.substring (10).trim(), true); countryCodes.trim(); countryCodes.removeEmptyStrings(); } } } void LocalisedStrings::setIgnoresCase (const bool shouldIgnoreCase) { translations.setIgnoresCase (shouldIgnoreCase); } void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) { const SpinLock::ScopedLockType sl (currentMappingsLock); currentMappings = newTranslations; } LocalisedStrings* LocalisedStrings::getCurrentMappings() { return currentMappings; } String LocalisedStrings::translateWithCurrentMappings (const String& text) { const SpinLock::ScopedLockType sl (currentMappingsLock); if (currentMappings != nullptr) return currentMappings->translate (text); return text; } String LocalisedStrings::translateWithCurrentMappings (const char* text) { return translateWithCurrentMappings (String (text)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_LocalisedStrings.cpp ***/ /*** Start of inlined file: juce_String.cpp ***/ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4996) #endif #include BEGIN_JUCE_NAMESPACE NewLine newLine; #if defined (JUCE_STRINGS_ARE_UNICODE) && ! JUCE_STRINGS_ARE_UNICODE #error "JUCE_STRINGS_ARE_UNICODE is deprecated! All strings are now unicode by default." #endif #if JUCE_NATIVE_WCHAR_IS_UTF8 typedef CharPointer_UTF8 CharPointer_wchar_t; #elif JUCE_NATIVE_WCHAR_IS_UTF16 typedef CharPointer_UTF16 CharPointer_wchar_t; #else typedef CharPointer_UTF32 CharPointer_wchar_t; #endif static inline CharPointer_wchar_t castToCharPointer_wchar_t (const void* t) noexcept { return CharPointer_wchar_t (static_cast (t)); } class StringHolder { public: StringHolder() : refCount (0x3fffffff), allocatedNumBytes (sizeof (*text)) { text[0] = 0; } typedef String::CharPointerType CharPointerType; typedef String::CharPointerType::CharType CharType; static const CharPointerType createUninitialisedBytes (const size_t numBytes) { StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); s->refCount.value = 0; s->allocatedNumBytes = numBytes; return CharPointerType (s->text); } template static const CharPointerType createFromCharPointer (const CharPointer& text) { if (text.getAddress() == nullptr || text.isEmpty()) return getEmpty(); CharPointer t (text); size_t bytesNeeded = sizeof (CharType); while (! t.isEmpty()) bytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); CharPointerType (dest).writeAll (text); return dest; } template static const CharPointerType createFromCharPointer (const CharPointer& text, size_t maxChars) { if (text.getAddress() == nullptr || text.isEmpty() || maxChars == 0) return getEmpty(); CharPointer end (text); size_t numChars = 0; size_t bytesNeeded = sizeof (CharType); while (numChars < maxChars && ! end.isEmpty()) { bytesNeeded += CharPointerType::getBytesRequiredFor (end.getAndAdvance()); ++numChars; } const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); CharPointerType (dest).writeWithCharLimit (text, (int) numChars + 1); return dest; } template static const CharPointerType createFromCharPointer (const CharPointer& start, const CharPointer& end) { if (start.getAddress() == nullptr || start.isEmpty()) return getEmpty(); CharPointer e (start); int numChars = 0; size_t bytesNeeded = sizeof (CharType); while (e < end && ! e.isEmpty()) { bytesNeeded += CharPointerType::getBytesRequiredFor (e.getAndAdvance()); ++numChars; } const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); CharPointerType (dest).writeWithCharLimit (start, numChars + 1); return dest; } static const CharPointerType createFromFixedLength (const char* const src, const size_t numChars) { const CharPointerType dest (createUninitialisedBytes (numChars * sizeof (CharType) + sizeof (CharType))); CharPointerType (dest).writeWithCharLimit (CharPointer_UTF8 (src), (int) (numChars + 1)); return dest; } static inline const CharPointerType getEmpty() noexcept { return CharPointerType (empty.text); } static void retain (const CharPointerType& text) noexcept { ++(bufferFromText (text)->refCount); } static inline void release (StringHolder* const b) noexcept { if (--(b->refCount) == -1 && b != &empty) delete[] reinterpret_cast (b); } static void release (const CharPointerType& text) noexcept { release (bufferFromText (text)); } static CharPointerType makeUnique (const CharPointerType& text) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0) return text; CharPointerType newText (createUninitialisedBytes (b->allocatedNumBytes)); memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); release (b); return newText; } static CharPointerType makeUniqueWithByteSize (const CharPointerType& text, size_t numBytes) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0 && b->allocatedNumBytes >= numBytes) return text; CharPointerType newText (createUninitialisedBytes (jmax (b->allocatedNumBytes, numBytes))); memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); release (b); return newText; } static size_t getAllocatedNumBytes (const CharPointerType& text) noexcept { return bufferFromText (text)->allocatedNumBytes; } Atomic refCount; size_t allocatedNumBytes; CharType text[1]; static StringHolder empty; private: static inline StringHolder* bufferFromText (const CharPointerType& text) noexcept { // (Can't use offsetof() here because of warnings about this not being a POD) return reinterpret_cast (reinterpret_cast (text.getAddress()) - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); } void compileTimeChecks() { // Let me know if any of these assertions fail on your system! #if JUCE_NATIVE_WCHAR_IS_UTF8 static_jassert (sizeof (wchar_t) == 1); #elif JUCE_NATIVE_WCHAR_IS_UTF16 static_jassert (sizeof (wchar_t) == 2); #elif JUCE_NATIVE_WCHAR_IS_UTF32 static_jassert (sizeof (wchar_t) == 4); #else #error "native wchar_t size is unknown" #endif } }; StringHolder StringHolder::empty; const String String::empty; void String::preallocateBytes (const size_t numBytesNeeded) { text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType)); } String::String() noexcept : text (StringHolder::getEmpty()) { } String::~String() noexcept { StringHolder::release (text); } String::String (const String& other) noexcept : text (other.text) { StringHolder::retain (text); } void String::swapWith (String& other) noexcept { std::swap (text, other.text); } String& String::operator= (const String& other) noexcept { StringHolder::retain (other.text); StringHolder::release (text.atomicSwap (other.text)); return *this; } inline String::PreallocationBytes::PreallocationBytes (const size_t numBytes_) : numBytes (numBytes_) {} String::String (const PreallocationBytes& preallocationSize) : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType))) { } String::String (const char* const t) : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t))) { /* If you get an assertion here, then you're trying to create a string from 8-bit data that contains values greater than 127. These can NOT be correctly converted to unicode because there's no way for the String class to know what encoding was used to create them. The source data could be UTF-8, ASCII or one of many local code-pages. To get around this problem, you must be more explicit when you pass an ambiguous 8-bit string to the String class - so for example if your source data is actually UTF-8, you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to correctly convert the multi-byte characters to unicode. It's *highly* recommended that you use UTF-8 with escape characters in your source code to represent extended characters, because there's no other way to represent these strings in a way that isn't dependent on the compiler, source code editor and platform. */ jassert (t == nullptr || CharPointer_ASCII::isValidString (t, std::numeric_limits::max())); } String::String (const char* const t, const size_t maxChars) : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t), maxChars)) { /* If you get an assertion here, then you're trying to create a string from 8-bit data that contains values greater than 127. These can NOT be correctly converted to unicode because there's no way for the String class to know what encoding was used to create them. The source data could be UTF-8, ASCII or one of many local code-pages. To get around this problem, you must be more explicit when you pass an ambiguous 8-bit string to the String class - so for example if your source data is actually UTF-8, you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to correctly convert the multi-byte characters to unicode. It's *highly* recommended that you use UTF-8 with escape characters in your source code to represent extended characters, because there's no other way to represent these strings in a way that isn't dependent on the compiler, source code editor and platform. */ jassert (t == nullptr || CharPointer_ASCII::isValidString (t, (int) maxChars)); } String::String (const wchar_t* const t) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t))) {} String::String (const CharPointer_UTF8& t) : text (StringHolder::createFromCharPointer (t)) {} String::String (const CharPointer_UTF16& t) : text (StringHolder::createFromCharPointer (t)) {} String::String (const CharPointer_UTF32& t) : text (StringHolder::createFromCharPointer (t)) {} String::String (const CharPointer_ASCII& t) : text (StringHolder::createFromCharPointer (t)) {} String::String (const CharPointer_UTF8& t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} String::String (const CharPointer_UTF16& t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} String::String (const CharPointer_UTF32& t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} String::String (const wchar_t* const t, size_t maxChars) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t), maxChars)) {} String::String (const CharPointer_UTF8& start, const CharPointer_UTF8& end) : text (StringHolder::createFromCharPointer (start, end)) {} String::String (const CharPointer_UTF16& start, const CharPointer_UTF16& end) : text (StringHolder::createFromCharPointer (start, end)) {} String::String (const CharPointer_UTF32& start, const CharPointer_UTF32& end) : text (StringHolder::createFromCharPointer (start, end)) {} String String::charToString (const juce_wchar character) { String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character))); CharPointerType t (result.text); t.write (character); t.writeNull(); return result; } namespace NumberToStringConverters { // pass in a pointer to the END of a buffer.. char* numberToString (char* t, const int64 n) noexcept { *--t = 0; int64 v = (n >= 0) ? n : -n; do { *--t = (char) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } char* numberToString (char* t, uint64 v) noexcept { *--t = 0; do { *--t = (char) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); return t; } char* numberToString (char* t, const int n) noexcept { if (n == (int) 0x80000000) // (would cause an overflow) return numberToString (t, (int64) n); *--t = 0; int v = abs (n); do { *--t = (char) ('0' + (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } char* numberToString (char* t, unsigned int v) noexcept { *--t = 0; do { *--t = (char) ('0' + (v % 10)); v /= 10; } while (v > 0); return t; } char getDecimalPoint() { static char dp = (char) #if JUCE_VC7_OR_EARLIER std::_USE (std::locale(), std::numpunct ).decimal_point(); #else std::use_facet > (std::locale()).decimal_point(); #endif return dp; } char* doubleToString (char* buffer, int numChars, double n, int numDecPlaces, size_t& len) noexcept { if (numDecPlaces > 0 && n > -1.0e20 && n < 1.0e20) { char* const end = buffer + numChars; char* t = end; int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); *--t = (char) 0; while (numDecPlaces >= 0 || v > 0) { if (numDecPlaces == 0) *--t = (char) getDecimalPoint(); *--t = (char) ('0' + (v % 10)); v /= 10; --numDecPlaces; } if (n < 0) *--t = '-'; len = end - t - 1; return t; } else { len = sprintf (buffer, "%.9g", n); return buffer; } } template const String::CharPointerType createFromInteger (const IntegerType number) { char buffer [32]; char* const end = buffer + numElementsInArray (buffer); char* const start = numberToString (end, number); return StringHolder::createFromFixedLength (start, end - start - 1); } const String::CharPointerType createFromDouble (const double number, const int numberOfDecimalPlaces) { char buffer [48]; size_t len; char* const start = doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); return StringHolder::createFromFixedLength (start, len); } } String::String (const int number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const unsigned int number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const short number) : text (NumberToStringConverters::createFromInteger ((int) number)) {} String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const float number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble ((double) number, numberOfDecimalPlaces)) {} String::String (const double number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble (number, numberOfDecimalPlaces)) {} int String::length() const noexcept { return (int) text.length(); } size_t String::getByteOffsetOfEnd() const noexcept { return ((char*) text.findTerminatingNull().getAddress()) - (char*) text.getAddress(); } const juce_wchar String::operator[] (int index) const noexcept { jassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo (index + 1))); return text [index]; } int String::hashCode() const noexcept { CharPointerType t (text); int result = 0; while (! t.isEmpty()) result = 31 * result + t.getAndAdvance(); return result; } int64 String::hashCode64() const noexcept { CharPointerType t (text); int64 result = 0; while (! t.isEmpty()) result = 101 * result + t.getAndAdvance(); return result; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const char* const s2) noexcept { return s1.compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF8& s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF16& s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF32& s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const char* const s2) noexcept { return s1.compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF8& s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF16& s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF32& s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } JUCE_API bool JUCE_CALLTYPE operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; } JUCE_API bool JUCE_CALLTYPE operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; } JUCE_API bool JUCE_CALLTYPE operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; } JUCE_API bool JUCE_CALLTYPE operator<= (const String& s1, const String& s2) noexcept { return s1.compare (s2) <= 0; } bool String::equalsIgnoreCase (const wchar_t* const t) const noexcept { return t != nullptr ? text.compareIgnoreCase (castToCharPointer_wchar_t (t)) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const char* const t) const noexcept { return t != nullptr ? text.compareIgnoreCase (CharPointer_UTF8 (t)) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const String& other) const noexcept { return text == other.text || text.compareIgnoreCase (other.text) == 0; } int String::compare (const String& other) const noexcept { return (text == other.text) ? 0 : text.compare (other.text); } int String::compare (const char* const other) const noexcept { return text.compare (CharPointer_UTF8 (other)); } int String::compare (const wchar_t* const other) const noexcept { return text.compare (castToCharPointer_wchar_t (other)); } int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); } int String::compareLexicographically (const String& other) const noexcept { CharPointerType s1 (text); while (! (s1.isEmpty() || s1.isLetterOrDigit())) ++s1; CharPointerType s2 (other.text); while (! (s2.isEmpty() || s2.isLetterOrDigit())) ++s2; return s1.compareIgnoreCase (s2); } void String::append (const String& textToAppend, size_t maxCharsToTake) { appendCharPointer (textToAppend.text, maxCharsToTake); } String& String::operator+= (const wchar_t* const t) { appendCharPointer (castToCharPointer_wchar_t (t)); return *this; } String& String::operator+= (const char* const t) { /* If you get an assertion here, then you're trying to create a string from 8-bit data that contains values greater than 127. These can NOT be correctly converted to unicode because there's no way for the String class to know what encoding was used to create them. The source data could be UTF-8, ASCII or one of many local code-pages. To get around this problem, you must be more explicit when you pass an ambiguous 8-bit string to the String class - so for example if your source data is actually UTF-8, you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to correctly convert the multi-byte characters to unicode. It's *highly* recommended that you use UTF-8 with escape characters in your source code to represent extended characters, because there's no other way to represent these strings in a way that isn't dependent on the compiler, source code editor and platform. */ jassert (t == nullptr || CharPointer_ASCII::isValidString (t, std::numeric_limits::max())); appendCharPointer (CharPointer_ASCII (t)); return *this; } String& String::operator+= (const String& other) { if (isEmpty()) return operator= (other); appendCharPointer (other.text); return *this; } String& String::operator+= (const char ch) { const char asString[] = { ch, 0 }; return operator+= (asString); } String& String::operator+= (const wchar_t ch) { const wchar_t asString[] = { ch, 0 }; return operator+= (asString); } #if ! JUCE_NATIVE_WCHAR_IS_UTF32 String& String::operator+= (const juce_wchar ch) { const juce_wchar asString[] = { ch, 0 }; appendCharPointer (CharPointer_UTF32 (asString)); return *this; } #endif String& String::operator+= (const int number) { char buffer [16]; char* const end = buffer + numElementsInArray (buffer); char* const start = NumberToStringConverters::numberToString (end, number); const int numExtraChars = (int) (end - start); if (numExtraChars > 0) { const size_t byteOffsetOfNull = getByteOffsetOfEnd(); const size_t newBytesNeeded = sizeof (CharPointerType::CharType) + byteOffsetOfNull + sizeof (CharPointerType::CharType) * numExtraChars; text = StringHolder::makeUniqueWithByteSize (text, newBytesNeeded); CharPointerType newEnd (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)); newEnd.writeWithCharLimit (CharPointer_ASCII (start), numExtraChars); } return *this; } JUCE_API String JUCE_CALLTYPE operator+ (const char* const string1, const String& string2) { String s (string1); return s += string2; } JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* const string1, const String& string2) { String s (string1); return s += string2; } JUCE_API String JUCE_CALLTYPE operator+ (const char s1, const String& s2) { return String::charToString (s1) + s2; } JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t s1, const String& s2) { return String::charToString (s1) + s2; } #if ! JUCE_NATIVE_WCHAR_IS_UTF32 JUCE_API String JUCE_CALLTYPE operator+ (const juce_wchar s1, const String& s2) { return String::charToString (s1) + s2; } #endif JUCE_API String JUCE_CALLTYPE operator+ (String s1, const String& s2) { return s1 += s2; } JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char* const s2) { return s1 += s2; } JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t* s2) { return s1 += s2; } JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char s2) { return s1 += s2; } JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t s2) { return s1 += s2; } #if ! JUCE_NATIVE_WCHAR_IS_UTF32 JUCE_API String JUCE_CALLTYPE operator+ (String s1, const juce_wchar s2) { return s1 += s2; } #endif JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char s2) { return s1 += s2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t s2) { return s1 += s2; } #if ! JUCE_NATIVE_WCHAR_IS_UTF32 JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const juce_wchar s2) { return s1 += s2; } #endif JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t* const s2) { return s1 += s2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) { const int numBytes = text.getNumBytesAsUTF8(); #if (JUCE_STRING_UTF_TYPE == 8) stream.write (text.getCharPointer().getAddress(), numBytes); #else // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind // if lots of large, persistent strings were to be written to streams). HeapBlock temp (numBytes + 1); CharPointer_UTF8 (temp).writeAll (text.getCharPointer()); stream.write (temp, numBytes); #endif return stream; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&) { return string1 += NewLine::getDefault(); } int String::indexOfChar (const juce_wchar character) const noexcept { return text.indexOf (character); } int String::indexOfChar (const int startIndex, const juce_wchar character) const noexcept { CharPointerType t (text); for (int i = 0; ! t.isEmpty(); ++i) { if (i >= startIndex) { if (t.getAndAdvance() == character) return i; } else { ++t; } } return -1; } int String::lastIndexOfChar (const juce_wchar character) const noexcept { CharPointerType t (text); int last = -1; for (int i = 0; ! t.isEmpty(); ++i) if (t.getAndAdvance() == character) last = i; return last; } int String::indexOfAnyOf (const String& charactersToLookFor, const int startIndex, const bool ignoreCase) const noexcept { CharPointerType t (text); for (int i = 0; ! t.isEmpty(); ++i) { if (i >= startIndex) { if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) return i; } else { ++t; } } return -1; } int String::indexOf (const String& other) const noexcept { return other.isEmpty() ? 0 : text.indexOf (other.text); } int String::indexOfIgnoreCase (const String& other) const noexcept { return other.isEmpty() ? 0 : CharacterFunctions::indexOfIgnoreCase (text, other.text); } int String::indexOf (const int startIndex, const String& other) const noexcept { if (other.isEmpty()) return -1; CharPointerType t (text); for (int i = startIndex; --i >= 0;) { if (t.isEmpty()) return -1; ++t; } int found = t.indexOf (other.text); if (found >= 0) found += startIndex; return found; } int String::indexOfIgnoreCase (const int startIndex, const String& other) const noexcept { if (other.isEmpty()) return -1; CharPointerType t (text); for (int i = startIndex; --i >= 0;) { if (t.isEmpty()) return -1; ++t; } int found = CharacterFunctions::indexOfIgnoreCase (t, other.text); if (found >= 0) found += startIndex; return found; } int String::lastIndexOf (const String& other) const noexcept { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { CharPointerType n (text + i); while (i >= 0) { if (n.compareUpTo (other.text, len) == 0) return i; --n; --i; } } } return -1; } int String::lastIndexOfIgnoreCase (const String& other) const noexcept { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { CharPointerType n (text + i); while (i >= 0) { if (n.compareIgnoreCaseUpTo (other.text, len) == 0) return i; --n; --i; } } } return -1; } int String::lastIndexOfAnyOf (const String& charactersToLookFor, const bool ignoreCase) const noexcept { CharPointerType t (text); int last = -1; for (int i = 0; ! t.isEmpty(); ++i) if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) last = i; return last; } bool String::contains (const String& other) const noexcept { return indexOf (other) >= 0; } bool String::containsChar (const juce_wchar character) const noexcept { return text.indexOf (character) >= 0; } bool String::containsIgnoreCase (const String& t) const noexcept { return indexOfIgnoreCase (t) >= 0; } int String::indexOfWholeWord (const String& word) const noexcept { if (word.isNotEmpty()) { CharPointerType t (text); const int wordLen = word.length(); const int end = (int) t.length() - wordLen; for (int i = 0; i <= end; ++i) { if (t.compareUpTo (word.text, wordLen) == 0 && (i == 0 || ! (t - 1).isLetterOrDigit()) && ! (t + wordLen).isLetterOrDigit()) return i; ++t; } } return -1; } int String::indexOfWholeWordIgnoreCase (const String& word) const noexcept { if (word.isNotEmpty()) { CharPointerType t (text); const int wordLen = word.length(); const int end = (int) t.length() - wordLen; for (int i = 0; i <= end; ++i) { if (t.compareIgnoreCaseUpTo (word.text, wordLen) == 0 && (i == 0 || ! (t - 1).isLetterOrDigit()) && ! (t + wordLen).isLetterOrDigit()) return i; ++t; } } return -1; } bool String::containsWholeWord (const String& wordToLookFor) const noexcept { return indexOfWholeWord (wordToLookFor) >= 0; } bool String::containsWholeWordIgnoreCase (const String& wordToLookFor) const noexcept { return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; } namespace WildCardHelpers { int indexOfMatch (const String::CharPointerType& wildcard, String::CharPointerType test, const bool ignoreCase) noexcept { int start = 0; while (! test.isEmpty()) { String::CharPointerType t (test); String::CharPointerType w (wildcard); for (;;) { const juce_wchar wc = *w; const juce_wchar tc = *t; if (wc == tc || (ignoreCase && w.toLowerCase() == t.toLowerCase()) || (wc == '?' && tc != 0)) { if (wc == 0) return start; ++t; ++w; } else { if (wc == '*' && (w[1] == 0 || indexOfMatch (w + 1, t, ignoreCase) >= 0)) return start; break; } } ++start; ++test; } return -1; } } bool String::matchesWildcard (const String& wildcard, const bool ignoreCase) const noexcept { CharPointerType w (wildcard.text); CharPointerType t (text); for (;;) { const juce_wchar wc = *w; const juce_wchar tc = *t; if (wc == tc || (ignoreCase && w.toLowerCase() == t.toLowerCase()) || (wc == '?' && tc != 0)) { if (wc == 0) return true; ++w; ++t; } else { return wc == '*' && (w[1] == 0 || WildCardHelpers::indexOfMatch (w + 1, t, ignoreCase) >= 0); } } } String String::repeatedString (const String& stringToRepeat, int numberOfTimesToRepeat) { if (numberOfTimesToRepeat <= 0) return String::empty; String result (PreallocationBytes (stringToRepeat.getByteOffsetOfEnd() * numberOfTimesToRepeat)); CharPointerType n (result.text); while (--numberOfTimesToRepeat >= 0) n.writeAll (stringToRepeat.text); return result; } String String::paddedLeft (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); int extraChars = minimumLength; CharPointerType end (text); while (! end.isEmpty()) { --extraChars; ++end; } if (extraChars <= 0 || padCharacter == 0) return *this; const size_t currentByteSize = ((char*) end.getAddress()) - (char*) text.getAddress(); String result (PreallocationBytes (currentByteSize + extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); CharPointerType n (result.text); while (--extraChars >= 0) n.write (padCharacter); n.writeAll (text); return result; } String String::paddedRight (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); int extraChars = minimumLength; CharPointerType end (text); while (! end.isEmpty()) { --extraChars; ++end; } if (extraChars <= 0 || padCharacter == 0) return *this; const size_t currentByteSize = ((char*) end.getAddress()) - (char*) text.getAddress(); String result (PreallocationBytes (currentByteSize + extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); CharPointerType n (result.text); n.writeAll (text); while (--extraChars >= 0) n.write (padCharacter); n.writeNull(); return result; } String String::replaceSection (int index, int numCharsToReplace, const String& stringToInsert) const { if (index < 0) { // a negative index to replace from? jassertfalse; index = 0; } if (numCharsToReplace < 0) { // replacing a negative number of characters? numCharsToReplace = 0; jassertfalse; } int i = 0; CharPointerType insertPoint (text); while (i < index) { if (insertPoint.isEmpty()) { // replacing beyond the end of the string? jassertfalse; return *this + stringToInsert; } ++insertPoint; ++i; } CharPointerType startOfRemainder (insertPoint); i = 0; while (i < numCharsToReplace && ! startOfRemainder.isEmpty()) { ++startOfRemainder; ++i; } if (insertPoint == text && startOfRemainder.isEmpty()) return stringToInsert; const size_t initialBytes = ((char*) insertPoint.getAddress()) - (char*) text.getAddress(); const size_t newStringBytes = stringToInsert.getByteOffsetOfEnd(); const size_t remainderBytes = ((char*) startOfRemainder.findTerminatingNull().getAddress()) - (char*) startOfRemainder.getAddress(); const size_t newTotalBytes = initialBytes + newStringBytes + remainderBytes; if (newTotalBytes <= 0) return String::empty; String result (PreallocationBytes ((size_t) newTotalBytes)); char* dest = (char*) result.text.getAddress(); memcpy (dest, text.getAddress(), initialBytes); dest += initialBytes; memcpy (dest, stringToInsert.text.getAddress(), newStringBytes); dest += newStringBytes; memcpy (dest, startOfRemainder.getAddress(), remainderBytes); dest += remainderBytes; CharPointerType ((CharPointerType::CharType*) dest).writeNull(); return result; } String String::replace (const String& stringToReplace, const String& stringToInsert, const bool ignoreCase) const { const int stringToReplaceLen = stringToReplace.length(); const int stringToInsertLen = stringToInsert.length(); int i = 0; String result (*this); while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace) : result.indexOf (i, stringToReplace))) >= 0) { result = result.replaceSection (i, stringToReplaceLen, stringToInsert); i += stringToInsertLen; } return result; } class StringCreationHelper { public: StringCreationHelper (const size_t initialBytes) : source (nullptr), dest (nullptr), allocatedBytes (initialBytes), bytesWritten (0) { result.preallocateBytes (allocatedBytes); dest = result.getCharPointer(); } StringCreationHelper (const String::CharPointerType& source_) : source (source_), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (source)), bytesWritten (0) { result.preallocateBytes (allocatedBytes); dest = result.getCharPointer(); } void write (juce_wchar c) { bytesWritten += String::CharPointerType::getBytesRequiredFor (c); if (bytesWritten > allocatedBytes) { allocatedBytes += jmax ((size_t) 8, allocatedBytes / 16); const size_t destOffset = ((char*) dest.getAddress()) - (char*) result.getCharPointer().getAddress(); result.preallocateBytes (allocatedBytes); dest = addBytesToPointer (result.getCharPointer().getAddress(), (int) destOffset); } dest.write (c); } String result; String::CharPointerType source; private: String::CharPointerType dest; size_t allocatedBytes, bytesWritten; }; String String::replaceCharacter (const juce_wchar charToReplace, const juce_wchar charToInsert) const { if (! containsChar (charToReplace)) return *this; StringCreationHelper builder (text); for (;;) { juce_wchar c = builder.source.getAndAdvance(); if (c == charToReplace) c = charToInsert; builder.write (c); if (c == 0) break; } return builder.result; } String String::replaceCharacters (const String& charactersToReplace, const String& charactersToInsertInstead) const { StringCreationHelper builder (text); for (;;) { juce_wchar c = builder.source.getAndAdvance(); const int index = charactersToReplace.indexOfChar (c); if (index >= 0) c = charactersToInsertInstead [index]; builder.write (c); if (c == 0) break; } return builder.result; } bool String::startsWith (const String& other) const noexcept { return text.compareUpTo (other.text, other.length()) == 0; } bool String::startsWithIgnoreCase (const String& other) const noexcept { return text.compareIgnoreCaseUpTo (other.text, other.length()) == 0; } bool String::startsWithChar (const juce_wchar character) const noexcept { jassert (character != 0); // strings can't contain a null character! return *text == character; } bool String::endsWithChar (const juce_wchar character) const noexcept { jassert (character != 0); // strings can't contain a null character! if (text.isEmpty()) return false; CharPointerType t (text.findTerminatingNull()); return *--t == character; } bool String::endsWith (const String& other) const noexcept { CharPointerType end (text.findTerminatingNull()); CharPointerType otherEnd (other.text.findTerminatingNull()); while (end > text && otherEnd > other.text) { --end; --otherEnd; if (*end != *otherEnd) return false; } return otherEnd == other.text; } bool String::endsWithIgnoreCase (const String& other) const noexcept { CharPointerType end (text.findTerminatingNull()); CharPointerType otherEnd (other.text.findTerminatingNull()); while (end > text && otherEnd > other.text) { --end; --otherEnd; if (end.toLowerCase() != otherEnd.toLowerCase()) return false; } return otherEnd == other.text; } String String::toUpperCase() const { StringCreationHelper builder (text); for (;;) { const juce_wchar c = builder.source.toUpperCase(); ++(builder.source); builder.write (c); if (c == 0) break; } return builder.result; } String String::toLowerCase() const { StringCreationHelper builder (text); for (;;) { const juce_wchar c = builder.source.toLowerCase(); ++(builder.source); builder.write (c); if (c == 0) break; } return builder.result; } juce_wchar String::getLastCharacter() const noexcept { return isEmpty() ? juce_wchar() : text [length() - 1]; } String String::substring (int start, const int end) const { if (start < 0) start = 0; if (end <= start) return empty; int i = 0; CharPointerType t1 (text); while (i < start) { if (t1.isEmpty()) return empty; ++i; ++t1; } CharPointerType t2 (t1); while (i < end) { if (t2.isEmpty()) { if (start == 0) return *this; break; } ++i; ++t2; } return String (t1, t2); } String String::substring (int start) const { if (start <= 0) return *this; CharPointerType t (text); while (--start >= 0) { if (t.isEmpty()) return empty; ++t; } return String (t); } String String::dropLastCharacters (const int numberToDrop) const { return String (text, jmax (0, length() - numberToDrop)); } String String::getLastCharacters (const int numCharacters) const { return String (text + jmax (0, length() - jmax (0, numCharacters))); } String String::fromFirstOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? indexOfIgnoreCase (sub) : indexOf (sub); if (i < 0) return empty; return substring (includeSubString ? i : i + sub.length()); } String String::fromLastOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) : lastIndexOf (sub); if (i < 0) return *this; return substring (includeSubString ? i : i + sub.length()); } String String::upToFirstOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? indexOfIgnoreCase (sub) : indexOf (sub); if (i < 0) return *this; return substring (0, includeSubString ? i + sub.length() : i); } String String::upToLastOccurrenceOf (const String& sub, const bool includeSubString, const bool ignoreCase) const { const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) : lastIndexOf (sub); if (i < 0) return *this; return substring (0, includeSubString ? i + sub.length() : i); } bool String::isQuotedString() const { const String trimmed (trimStart()); return trimmed[0] == '"' || trimmed[0] == '\''; } String String::unquoted() const { const int len = length(); if (len == 0) return empty; const juce_wchar lastChar = text [len - 1]; const int dropAtStart = (*text == '"' || *text == '\'') ? 1 : 0; const int dropAtEnd = (lastChar == '"' || lastChar == '\'') ? 1 : 0; return substring (dropAtStart, len - dropAtEnd); } String String::quoted (const juce_wchar quoteCharacter) const { if (isEmpty()) return charToString (quoteCharacter) + quoteCharacter; String t (*this); if (! t.startsWithChar (quoteCharacter)) t = charToString (quoteCharacter) + t; if (! t.endsWithChar (quoteCharacter)) t += quoteCharacter; return t; } static String::CharPointerType findTrimmedEnd (const String::CharPointerType& start, String::CharPointerType end) { while (end > start) { if (! (--end).isWhitespace()) { ++end; break; } } return end; } String String::trim() const { if (isNotEmpty()) { CharPointerType start (text.findEndOfWhitespace()); const CharPointerType end (start.findTerminatingNull()); CharPointerType trimmedEnd (findTrimmedEnd (start, end)); if (trimmedEnd <= start) return empty; else if (text < start || trimmedEnd < end) return String (start, trimmedEnd); } return *this; } String String::trimStart() const { if (isNotEmpty()) { const CharPointerType t (text.findEndOfWhitespace()); if (t != text) return String (t); } return *this; } String String::trimEnd() const { if (isNotEmpty()) { const CharPointerType end (text.findTerminatingNull()); CharPointerType trimmedEnd (findTrimmedEnd (text, end)); if (trimmedEnd < end) return String (text, trimmedEnd); } return *this; } String String::trimCharactersAtStart (const String& charactersToTrim) const { CharPointerType t (text); while (charactersToTrim.containsChar (*t)) ++t; return t == text ? *this : String (t); } String String::trimCharactersAtEnd (const String& charactersToTrim) const { if (isNotEmpty()) { const CharPointerType end (text.findTerminatingNull()); CharPointerType trimmedEnd (end); while (trimmedEnd > text) { if (! charactersToTrim.containsChar (*--trimmedEnd)) { ++trimmedEnd; break; } } if (trimmedEnd < end) return String (text, trimmedEnd); } return *this; } String String::retainCharacters (const String& charactersToRetain) const { if (isEmpty()) return empty; StringCreationHelper builder (text); for (;;) { juce_wchar c = builder.source.getAndAdvance(); if (charactersToRetain.containsChar (c)) builder.write (c); if (c == 0) break; } builder.write (0); return builder.result; } String String::removeCharacters (const String& charactersToRemove) const { if (isEmpty()) return empty; StringCreationHelper builder (text); for (;;) { juce_wchar c = builder.source.getAndAdvance(); if (! charactersToRemove.containsChar (c)) builder.write (c); if (c == 0) break; } return builder.result; } String String::initialSectionContainingOnly (const String& permittedCharacters) const { CharPointerType t (text); while (! t.isEmpty()) { if (! permittedCharacters.containsChar (*t)) return String (text, t); ++t; } return *this; } String String::initialSectionNotContaining (const String& charactersToStopAt) const { CharPointerType t (text); while (! t.isEmpty()) { if (charactersToStopAt.containsChar (*t)) return String (text, t); ++t; } return *this; } bool String::containsOnly (const String& chars) const noexcept { CharPointerType t (text); while (! t.isEmpty()) if (! chars.containsChar (t.getAndAdvance())) return false; return true; } bool String::containsAnyOf (const String& chars) const noexcept { CharPointerType t (text); while (! t.isEmpty()) if (chars.containsChar (t.getAndAdvance())) return true; return false; } bool String::containsNonWhitespaceChars() const noexcept { CharPointerType t (text); while (! t.isEmpty()) { if (! t.isWhitespace()) return true; ++t; } return false; } // Note! The format parameter here MUST NOT be a reference, otherwise MS's va_start macro fails to work (but still compiles). String String::formatted (const String pf, ... ) { size_t bufferSize = 256; for (;;) { va_list args; va_start (args, pf); #if JUCE_WINDOWS HeapBlock temp (bufferSize); const int num = (int) _vsnwprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); #elif JUCE_ANDROID HeapBlock temp (bufferSize); const int num = (int) vsnprintf (temp.getData(), bufferSize - 1, pf.toUTF8(), args); #else HeapBlock temp (bufferSize); const int num = (int) vswprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); #endif va_end (args); if (num > 0) return String (temp); bufferSize += 256; if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vprintf repeatedly break; // returns -1 because of an error rather than because it needs more space. } return empty; } int String::getIntValue() const noexcept { return text.getIntValue32(); } int String::getTrailingIntValue() const noexcept { int n = 0; int mult = 1; CharPointerType t (text.findTerminatingNull()); while (--t >= text) { if (! t.isDigit()) { if (*t == '-') n = -n; break; } n += mult * (*t - '0'); mult *= 10; } return n; } int64 String::getLargeIntValue() const noexcept { return text.getIntValue64(); } float String::getFloatValue() const noexcept { return (float) getDoubleValue(); } double String::getDoubleValue() const noexcept { return text.getDoubleValue(); } static const char hexDigits[] = "0123456789abcdef"; template struct HexConverter { static String hexToString (Type v) { char buffer[32]; char* const end = buffer + 32; char* t = end; *--t = 0; do { *--t = hexDigits [(int) (v & 15)]; v >>= 4; } while (v != 0); return String (t, (int) (end - t) - 1); } static Type stringToHex (String::CharPointerType t) noexcept { Type result = 0; while (! t.isEmpty()) { const int hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); if (hexValue >= 0) result = (result << 4) | hexValue; } return result; } }; String String::toHexString (const int number) { return HexConverter ::hexToString ((unsigned int) number); } String String::toHexString (const int64 number) { return HexConverter ::hexToString ((uint64) number); } String String::toHexString (const short number) { return toHexString ((int) (unsigned short) number); } String String::toHexString (const void* const d, const int size, const int groupSize) { if (size <= 0) return empty; int numChars = (size * 2) + 2; if (groupSize > 0) numChars += size / groupSize; String s (PreallocationBytes (sizeof (CharPointerType::CharType) * (size_t) numChars)); const unsigned char* data = static_cast (d); CharPointerType dest (s.text); for (int i = 0; i < size; ++i) { const unsigned char nextByte = *data++; dest.write ((juce_wchar) hexDigits [nextByte >> 4]); dest.write ((juce_wchar) hexDigits [nextByte & 0xf]); if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) dest.write ((juce_wchar) ' '); } dest.writeNull(); return s; } int String::getHexValue32() const noexcept { return HexConverter::stringToHex (text); } int64 String::getHexValue64() const noexcept { return HexConverter::stringToHex (text); } String String::createStringFromData (const void* const data_, const int size) { const uint8* const data = static_cast (data_); if (size <= 0 || data == nullptr) { return empty; } else if (size == 1) { return charToString ((char) data[0]); } else if ((data[0] == (uint8) CharPointer_UTF16::byteOrderMarkBE1 && data[1] == (uint8) CharPointer_UTF16::byteOrderMarkBE2) || (data[0] == (uint8) CharPointer_UTF16::byteOrderMarkLE1 && data[1] == (uint8) CharPointer_UTF16::byteOrderMarkLE2)) { const bool bigEndian = (data[0] == (uint8) CharPointer_UTF16::byteOrderMarkBE1); const int numChars = size / 2 - 1; StringCreationHelper builder (numChars); const uint16* const src = (const uint16*) (data + 2); if (bigEndian) { for (int i = 0; i < numChars; ++i) builder.write ((juce_wchar) ByteOrder::swapIfLittleEndian (src[i])); } else { for (int i = 0; i < numChars; ++i) builder.write ((juce_wchar) ByteOrder::swapIfBigEndian (src[i])); } builder.write (0); return builder.result; } else { const uint8* start = data; const uint8* end = data + size; if (size >= 3 && data[0] == (uint8) CharPointer_UTF8::byteOrderMark1 && data[1] == (uint8) CharPointer_UTF8::byteOrderMark2 && data[2] == (uint8) CharPointer_UTF8::byteOrderMark3) start += 3; return String (CharPointer_UTF8 ((const char*) start), CharPointer_UTF8 ((const char*) end)); } } static juce_wchar emptyChar = 0; template struct StringEncodingConverter { static const CharPointerType_Dest convert (const String& s) { String& source = const_cast (s); typedef typename CharPointerType_Dest::CharType DestChar; if (source.isEmpty()) return CharPointerType_Dest (reinterpret_cast (&emptyChar)); CharPointerType_Src text (source.getCharPointer()); const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text); const size_t endOffset = (text.sizeInBytes() + 3) & ~3; // the new string must be word-aligned or many win32 // functions will fail to read it correctly! source.preallocateBytes (endOffset + extraBytesNeeded); text = source.getCharPointer(); void* newSpace = addBytesToPointer (text.getAddress(), (int) endOffset); const CharPointerType_Dest extraSpace (static_cast (newSpace)); #if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) const int bytesToClear = jmin ((int) extraBytesNeeded, 4); zeromem (addBytesToPointer (newSpace, (int) (extraBytesNeeded - bytesToClear)), bytesToClear); #endif CharPointerType_Dest (extraSpace).writeAll (text); return extraSpace; } }; template <> struct StringEncodingConverter { static const CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 ((CharPointer_UTF8::CharType*) source.getCharPointer().getAddress()); } }; template <> struct StringEncodingConverter { static const CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 ((CharPointer_UTF16::CharType*) source.getCharPointer().getAddress()); } }; template <> struct StringEncodingConverter { static const CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 ((CharPointer_UTF32::CharType*) source.getCharPointer().getAddress()); } }; const CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter ::convert (*this); } CharPointer_UTF16 String::toUTF16() const { return StringEncodingConverter ::convert (*this); } CharPointer_UTF32 String::toUTF32() const { return StringEncodingConverter ::convert (*this); } const wchar_t* String::toWideCharPointer() const { return (const wchar_t*) StringEncodingConverter ::convert (*this).getAddress(); } template struct StringCopier { static int copyToBuffer (const CharPointerType_Src& source, typename CharPointerType_Dest::CharType* const buffer, const int maxBufferSizeBytes) { jassert (maxBufferSizeBytes >= 0); // keep this value positive, or no characters will be copied! if (buffer == nullptr) return (int) (CharPointerType_Dest::getBytesRequiredFor (source) + sizeof (typename CharPointerType_Dest::CharType)); return CharPointerType_Dest (buffer).writeWithDestByteLimit (source, maxBufferSizeBytes); } }; int String::copyToUTF8 (CharPointer_UTF8::CharType* const buffer, const int maxBufferSizeBytes) const noexcept { return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); } int String::copyToUTF16 (CharPointer_UTF16::CharType* const buffer, int maxBufferSizeBytes) const noexcept { return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); } int String::copyToUTF32 (CharPointer_UTF32::CharType* const buffer, int maxBufferSizeBytes) const noexcept { return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); } int String::getNumBytesAsUTF8() const noexcept { return (int) CharPointer_UTF8::getBytesRequiredFor (text); } String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) { if (buffer != nullptr) { if (bufferSizeBytes < 0) return String (CharPointer_UTF8 (buffer)); else if (bufferSizeBytes > 0) return String (CharPointer_UTF8 (buffer), CharPointer_UTF8 (buffer + bufferSizeBytes)); } return String::empty; } #if JUCE_MSVC #pragma warning (pop) #endif String::Concatenator::Concatenator (String& stringToAppendTo) : result (stringToAppendTo), nextIndex (stringToAppendTo.length()) { } String::Concatenator::~Concatenator() { } void String::Concatenator::append (const String& s) { const int len = s.length(); if (len > 0) { size_t extraBytes = s.getCharPointer().sizeInBytes(); result.preallocateBytes (nextIndex + extraBytes); CharPointerType (result.text + nextIndex).writeAll (s.text); nextIndex += len; } } #if JUCE_UNIT_TESTS class StringTests : public UnitTest { public: StringTests() : UnitTest ("String class") {} template struct TestUTFConversion { static void test (UnitTest& test) { String s (createRandomWideCharString()); typename CharPointerType::CharType buffer [300]; memset (buffer, 0xff, sizeof (buffer)); CharPointerType (buffer).writeAll (s.toUTF32()); test.expectEquals (String (CharPointerType (buffer)), s); memset (buffer, 0xff, sizeof (buffer)); CharPointerType (buffer).writeAll (s.toUTF16()); test.expectEquals (String (CharPointerType (buffer)), s); memset (buffer, 0xff, sizeof (buffer)); CharPointerType (buffer).writeAll (s.toUTF8()); test.expectEquals (String (CharPointerType (buffer)), s); } }; static String createRandomWideCharString() { juce_wchar buffer[50] = { 0 }; for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) { if (Random::getSystemRandom().nextBool()) { do { buffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (0x10ffff - 1)); } while (! CharPointer_UTF16::canRepresent (buffer[i])); } else buffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (0xff)); } return CharPointer_UTF32 (buffer); } void runTest() { { beginTest ("Basics"); expect (String().length() == 0); expect (String() == String::empty); String s1, s2 ("abcd"); expect (s1.isEmpty() && ! s1.isNotEmpty()); expect (s2.isNotEmpty() && ! s2.isEmpty()); expect (s2.length() == 4); s1 = "abcd"; expect (s2 == s1 && s1 == s2); expect (s1 == "abcd" && s1 == L"abcd"); expect (String ("abcd") == String (L"abcd")); expect (String ("abcdefg", 4) == L"abcd"); expect (String ("abcdefg", 4) == String (L"abcdefg", 4)); expect (String::charToString ('x') == "x"); expect (String::charToString (0) == String::empty); expect (s2 + "e" == "abcde" && s2 + 'e' == "abcde"); expect (s2 + L'e' == "abcde" && s2 + L"e" == "abcde"); expect (s1.equalsIgnoreCase ("abcD") && s1 < "abce" && s1 > "abbb"); expect (s1.startsWith ("ab") && s1.startsWith ("abcd") && ! s1.startsWith ("abcde")); expect (s1.startsWithIgnoreCase ("aB") && s1.endsWithIgnoreCase ("CD")); expect (s1.endsWith ("bcd") && ! s1.endsWith ("aabcd")); expectEquals (s1.indexOf (String::empty), 0); expectEquals (s1.indexOfIgnoreCase (String::empty), 0); expect (s1.startsWith (String::empty) && s1.endsWith (String::empty) && s1.contains (String::empty)); expect (s1.contains ("cd") && s1.contains ("ab") && s1.contains ("abcd")); expect (s1.containsChar ('a')); expect (! s1.containsChar ('x')); expect (! s1.containsChar (0)); expect (String ("abc foo bar").containsWholeWord ("abc") && String ("abc foo bar").containsWholeWord ("abc")); } { beginTest ("Operations"); String s ("012345678"); expect (s.hashCode() != 0); expect (s.hashCode64() != 0); expect (s.hashCode() != (s + s).hashCode()); expect (s.hashCode64() != (s + s).hashCode64()); expect (s.compare (String ("012345678")) == 0); expect (s.compare (String ("012345679")) < 0); expect (s.compare (String ("012345676")) > 0); expect (s.substring (2, 3) == String::charToString (s[2])); expect (s.substring (0, 1) == String::charToString (s[0])); expect (s.getLastCharacter() == s [s.length() - 1]); expect (String::charToString (s.getLastCharacter()) == s.getLastCharacters (1)); expect (s.substring (0, 3) == L"012"); expect (s.substring (0, 100) == s); expect (s.substring (-1, 100) == s); expect (s.substring (3) == "345678"); expect (s.indexOf (L"45") == 4); expect (String ("444445").indexOf ("45") == 4); expect (String ("444445").lastIndexOfChar ('4') == 4); expect (String ("45454545x").lastIndexOf (L"45") == 6); expect (String ("45454545x").lastIndexOfAnyOf ("456") == 7); expect (String ("45454545x").lastIndexOfAnyOf (L"456x") == 8); expect (String ("abABaBaBa").lastIndexOfIgnoreCase ("aB") == 6); expect (s.indexOfChar (L'4') == 4); expect (s + s == "012345678012345678"); expect (s.startsWith (s)); expect (s.startsWith (s.substring (0, 4))); expect (s.startsWith (s.dropLastCharacters (4))); expect (s.endsWith (s.substring (5))); expect (s.endsWith (s)); expect (s.contains (s.substring (3, 6))); expect (s.contains (s.substring (3))); expect (s.startsWithChar (s[0])); expect (s.endsWithChar (s.getLastCharacter())); expect (s [s.length()] == 0); expect (String ("abcdEFGH").toLowerCase() == String ("abcdefgh")); expect (String ("abcdEFGH").toUpperCase() == String ("ABCDEFGH")); String s2 ("123"); s2 << ((int) 4) << ((short) 5) << "678" << L"9" << '0'; s2 += "xyz"; expect (s2 == "1234567890xyz"); beginTest ("Numeric conversions"); expect (String::empty.getIntValue() == 0); expect (String::empty.getDoubleValue() == 0.0); expect (String::empty.getFloatValue() == 0.0f); expect (s.getIntValue() == 12345678); expect (s.getLargeIntValue() == (int64) 12345678); expect (s.getDoubleValue() == 12345678.0); expect (s.getFloatValue() == 12345678.0f); expect (String (-1234).getIntValue() == -1234); expect (String ((int64) -1234).getLargeIntValue() == -1234); expect (String (-1234.56).getDoubleValue() == -1234.56); expect (String (-1234.56f).getFloatValue() == -1234.56f); expect (("xyz" + s).getTrailingIntValue() == s.getIntValue()); expect (s.getHexValue32() == 0x12345678); expect (s.getHexValue64() == (int64) 0x12345678); expect (String::toHexString (0x1234abcd).equalsIgnoreCase ("1234abcd")); expect (String::toHexString ((int64) 0x1234abcd).equalsIgnoreCase ("1234abcd")); expect (String::toHexString ((short) 0x12ab).equalsIgnoreCase ("12ab")); unsigned char data[] = { 1, 2, 3, 4, 0xa, 0xb, 0xc, 0xd }; expect (String::toHexString (data, 8, 0).equalsIgnoreCase ("010203040a0b0c0d")); expect (String::toHexString (data, 8, 1).equalsIgnoreCase ("01 02 03 04 0a 0b 0c 0d")); expect (String::toHexString (data, 8, 2).equalsIgnoreCase ("0102 0304 0a0b 0c0d")); beginTest ("Subsections"); String s3; s3 = "abcdeFGHIJ"; expect (s3.equalsIgnoreCase ("ABCdeFGhiJ")); expect (s3.compareIgnoreCase (L"ABCdeFGhiJ") == 0); expect (s3.containsIgnoreCase (s3.substring (3))); expect (s3.indexOfAnyOf ("xyzf", 2, true) == 5); expect (s3.indexOfAnyOf (L"xyzf", 2, false) == -1); expect (s3.indexOfAnyOf ("xyzF", 2, false) == 5); expect (s3.containsAnyOf (L"zzzFs")); expect (s3.startsWith ("abcd")); expect (s3.startsWithIgnoreCase (L"abCD")); expect (s3.startsWith (String::empty)); expect (s3.startsWithChar ('a')); expect (s3.endsWith (String ("HIJ"))); expect (s3.endsWithIgnoreCase (L"Hij")); expect (s3.endsWith (String::empty)); expect (s3.endsWithChar (L'J')); expect (s3.indexOf ("HIJ") == 7); expect (s3.indexOf (L"HIJK") == -1); expect (s3.indexOfIgnoreCase ("hij") == 7); expect (s3.indexOfIgnoreCase (L"hijk") == -1); String s4 (s3); s4.append (String ("xyz123"), 3); expect (s4 == s3 + "xyz"); expect (String (1234) < String (1235)); expect (String (1235) > String (1234)); expect (String (1234) >= String (1234)); expect (String (1234) <= String (1234)); expect (String (1235) >= String (1234)); expect (String (1234) <= String (1235)); String s5 ("word word2 word3"); expect (s5.containsWholeWord (String ("word2"))); expect (s5.indexOfWholeWord ("word2") == 5); expect (s5.containsWholeWord (L"word")); expect (s5.containsWholeWord ("word3")); expect (s5.containsWholeWord (s5)); expect (s5.containsWholeWordIgnoreCase (L"Word2")); expect (s5.indexOfWholeWordIgnoreCase ("Word2") == 5); expect (s5.containsWholeWordIgnoreCase (L"Word")); expect (s5.containsWholeWordIgnoreCase ("Word3")); expect (! s5.containsWholeWordIgnoreCase (L"Wordx")); expect (!s5.containsWholeWordIgnoreCase ("xWord2")); expect (s5.containsNonWhitespaceChars()); expect (s5.containsOnly ("ordw23 ")); expect (! String (" \n\r\t").containsNonWhitespaceChars()); expect (s5.matchesWildcard (L"wor*", false)); expect (s5.matchesWildcard ("wOr*", true)); expect (s5.matchesWildcard (L"*word3", true)); expect (s5.matchesWildcard ("*word?", true)); expect (s5.matchesWildcard (L"Word*3", true)); expectEquals (s5.fromFirstOccurrenceOf (String::empty, true, false), s5); expectEquals (s5.fromFirstOccurrenceOf ("xword2", true, false), s5.substring (100)); expectEquals (s5.fromFirstOccurrenceOf (L"word2", true, false), s5.substring (5)); expectEquals (s5.fromFirstOccurrenceOf ("Word2", true, true), s5.substring (5)); expectEquals (s5.fromFirstOccurrenceOf ("word2", false, false), s5.getLastCharacters (6)); expectEquals (s5.fromFirstOccurrenceOf (L"Word2", false, true), s5.getLastCharacters (6)); expectEquals (s5.fromLastOccurrenceOf (String::empty, true, false), s5); expectEquals (s5.fromLastOccurrenceOf (L"wordx", true, false), s5); expectEquals (s5.fromLastOccurrenceOf ("word", true, false), s5.getLastCharacters (5)); expectEquals (s5.fromLastOccurrenceOf (L"worD", true, true), s5.getLastCharacters (5)); expectEquals (s5.fromLastOccurrenceOf ("word", false, false), s5.getLastCharacters (1)); expectEquals (s5.fromLastOccurrenceOf (L"worD", false, true), s5.getLastCharacters (1)); expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); expectEquals (s5.upToFirstOccurrenceOf ("word4", true, false), s5); expectEquals (s5.upToFirstOccurrenceOf (L"word2", true, false), s5.substring (0, 10)); expectEquals (s5.upToFirstOccurrenceOf ("Word2", true, true), s5.substring (0, 10)); expectEquals (s5.upToFirstOccurrenceOf (L"word2", false, false), s5.substring (0, 5)); expectEquals (s5.upToFirstOccurrenceOf ("Word2", false, true), s5.substring (0, 5)); expectEquals (s5.upToLastOccurrenceOf (String::empty, true, false), s5); expectEquals (s5.upToLastOccurrenceOf ("zword", true, false), s5); expectEquals (s5.upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); expectEquals (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); expectEquals (s5.upToLastOccurrenceOf ("Word", true, true), s5.dropLastCharacters (1)); expectEquals (s5.upToLastOccurrenceOf ("word", false, false), s5.dropLastCharacters (5)); expectEquals (s5.upToLastOccurrenceOf ("Word", false, true), s5.dropLastCharacters (5)); expectEquals (s5.replace ("word", L"xyz", false), String ("xyz xyz2 xyz3")); expect (s5.replace (L"Word", "xyz", true) == "xyz xyz2 xyz3"); expect (s5.dropLastCharacters (1).replace ("Word", String ("xyz"), true) == L"xyz xyz2 xyz"); expect (s5.replace ("Word", "", true) == " 2 3"); expectEquals (s5.replace ("Word2", L"xyz", true), String ("word xyz word3")); expect (s5.replaceCharacter (L'w', 'x') != s5); expectEquals (s5.replaceCharacter ('w', L'x').replaceCharacter ('x', 'w'), s5); expect (s5.replaceCharacters ("wo", "xy") != s5); expectEquals (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", L"wo"), s5); expectEquals (s5.retainCharacters ("1wordxya"), String ("wordwordword")); expect (s5.retainCharacters (String::empty).isEmpty()); expect (s5.removeCharacters ("1wordxya") == " 2 3"); expectEquals (s5.removeCharacters (String::empty), s5); expect (s5.initialSectionContainingOnly ("word") == L"word"); expect (String ("word").initialSectionContainingOnly ("word") == L"word"); expectEquals (s5.initialSectionNotContaining (String ("xyz ")), String ("word")); expectEquals (s5.initialSectionNotContaining (String (";[:'/")), s5); expect (! s5.isQuotedString()); expect (s5.quoted().isQuotedString()); expect (! s5.quoted().unquoted().isQuotedString()); expect (! String ("x'").isQuotedString()); expect (String ("'x").isQuotedString()); String s6 (" \t xyz \t\r\n"); expectEquals (s6.trim(), String ("xyz")); expect (s6.trim().trim() == "xyz"); expectEquals (s5.trim(), s5); expectEquals (s6.trimStart().trimEnd(), s6.trim()); expectEquals (s6.trimStart().trimEnd(), s6.trimEnd().trimStart()); expectEquals (s6.trimStart().trimStart().trimEnd().trimEnd(), s6.trimEnd().trimStart()); expect (s6.trimStart() != s6.trimEnd()); expectEquals (("\t\r\n " + s6 + "\t\n \r").trim(), s6.trim()); expect (String::repeatedString ("xyz", 3) == L"xyzxyzxyz"); } { beginTest ("UTF conversions"); TestUTFConversion ::test (*this); TestUTFConversion ::test (*this); TestUTFConversion ::test (*this); } { beginTest ("StringArray"); StringArray s; for (int i = 5; --i >= 0;) s.add (String (i)); expectEquals (s.joinIntoString ("-"), String ("4-3-2-1-0")); s.remove (2); expectEquals (s.joinIntoString ("--"), String ("4--3--1--0")); expectEquals (s.joinIntoString (String::empty), String ("4310")); s.clear(); expectEquals (s.joinIntoString ("x"), String::empty); } } }; static StringTests stringUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_String.cpp ***/ /*** Start of inlined file: juce_StringArray.cpp ***/ BEGIN_JUCE_NAMESPACE StringArray::StringArray() noexcept { } StringArray::StringArray (const StringArray& other) : strings (other.strings) { } StringArray::StringArray (const String& firstValue) { strings.add (firstValue); } namespace StringArrayHelpers { template void addArray (Array& dest, const CharType* const* strings) { if (strings != nullptr) while (*strings != nullptr) dest.add (*strings++); } template void addArray (Array& dest, const CharType* const* const strings, const int numberOfStrings) { for (int i = 0; i < numberOfStrings; ++i) dest.add (strings [i]); } } StringArray::StringArray (const char* const* const initialStrings) { StringArrayHelpers::addArray (strings, initialStrings); } StringArray::StringArray (const char* const* const initialStrings, const int numberOfStrings) { StringArrayHelpers::addArray (strings, initialStrings, numberOfStrings); } StringArray::StringArray (const wchar_t* const* const initialStrings) { StringArrayHelpers::addArray (strings, initialStrings); } StringArray::StringArray (const wchar_t* const* const initialStrings, const int numberOfStrings) { StringArrayHelpers::addArray (strings, initialStrings, numberOfStrings); } StringArray& StringArray::operator= (const StringArray& other) { strings = other.strings; return *this; } StringArray::~StringArray() { } bool StringArray::operator== (const StringArray& other) const noexcept { if (other.size() != size()) return false; for (int i = size(); --i >= 0;) if (other.strings.getReference(i) != strings.getReference(i)) return false; return true; } bool StringArray::operator!= (const StringArray& other) const noexcept { return ! operator== (other); } void StringArray::clear() { strings.clear(); } const String& StringArray::operator[] (const int index) const noexcept { if (isPositiveAndBelow (index, strings.size())) return strings.getReference (index); return String::empty; } String& StringArray::getReference (const int index) noexcept { jassert (isPositiveAndBelow (index, strings.size())); return strings.getReference (index); } void StringArray::add (const String& newString) { strings.add (newString); } void StringArray::insert (const int index, const String& newString) { strings.insert (index, newString); } void StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) { if (! contains (newString, ignoreCase)) add (newString); } void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) { if (startIndex < 0) { jassertfalse; startIndex = 0; } if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) numElementsToAdd = otherArray.size() - startIndex; while (--numElementsToAdd >= 0) strings.add (otherArray.strings.getReference (startIndex++)); } void StringArray::set (const int index, const String& newString) { strings.set (index, newString); } bool StringArray::contains (const String& stringToLookFor, const bool ignoreCase) const { if (ignoreCase) { for (int i = size(); --i >= 0;) if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) return true; } else { for (int i = size(); --i >= 0;) if (stringToLookFor == strings.getReference(i)) return true; } return false; } int StringArray::indexOf (const String& stringToLookFor, const bool ignoreCase, int i) const { if (i < 0) i = 0; const int numElements = size(); if (ignoreCase) { while (i < numElements) { if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) return i; ++i; } } else { while (i < numElements) { if (stringToLookFor == strings.getReference (i)) return i; ++i; } } return -1; } void StringArray::remove (const int index) { strings.remove (index); } void StringArray::removeString (const String& stringToRemove, const bool ignoreCase) { if (ignoreCase) { for (int i = size(); --i >= 0;) if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) strings.remove (i); } else { for (int i = size(); --i >= 0;) if (stringToRemove == strings.getReference (i)) strings.remove (i); } } void StringArray::removeRange (int startIndex, int numberToRemove) { strings.removeRange (startIndex, numberToRemove); } void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) { if (removeWhitespaceStrings) { for (int i = size(); --i >= 0;) if (! strings.getReference(i).containsNonWhitespaceChars()) strings.remove (i); } else { for (int i = size(); --i >= 0;) if (strings.getReference(i).isEmpty()) strings.remove (i); } } void StringArray::trim() { for (int i = size(); --i >= 0;) { String& s = strings.getReference(i); s = s.trim(); } } class InternalStringArrayComparator_CaseSensitive { public: static int compareElements (String& first, String& second) { return first.compare (second); } }; class InternalStringArrayComparator_CaseInsensitive { public: static int compareElements (String& first, String& second) { return first.compareIgnoreCase (second); } }; void StringArray::sort (const bool ignoreCase) { if (ignoreCase) { InternalStringArrayComparator_CaseInsensitive comp; strings.sort (comp); } else { InternalStringArrayComparator_CaseSensitive comp; strings.sort (comp); } } void StringArray::move (const int currentIndex, int newIndex) noexcept { strings.move (currentIndex, newIndex); } String StringArray::joinIntoString (const String& separator, int start, int numberToJoin) const { const int last = (numberToJoin < 0) ? size() : jmin (size(), start + numberToJoin); if (start < 0) start = 0; if (start >= last) return String::empty; if (start == last - 1) return strings.getReference (start); const size_t separatorBytes = separator.getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); size_t bytesNeeded = separatorBytes * (last - start - 1); for (int i = start; i < last; ++i) bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); String result; result.preallocateBytes (bytesNeeded); String::CharPointerType dest (result.getCharPointer()); while (start < last) { const String& s = strings.getReference (start); if (! s.isEmpty()) dest.writeAll (s.getCharPointer()); if (++start < last && separatorBytes > 0) dest.writeAll (separator.getCharPointer()); } dest.writeNull(); return result; } int StringArray::addTokens (const String& text, const bool preserveQuotedStrings) { return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); } int StringArray::addTokens (const String& text, const String& breakCharacters, const String& quoteCharacters) { int num = 0; String::CharPointerType t (text.getCharPointer()); while (! t.isEmpty()) { String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, breakCharacters.getCharPointer(), quoteCharacters.getCharPointer())); add (String (t, tokenEnd)); ++num; if (tokenEnd.isEmpty()) break; t = ++tokenEnd; } return num; } int StringArray::addLines (const String& sourceText) { int numLines = 0; String::CharPointerType text (sourceText.getCharPointer()); bool finished = text.isEmpty(); while (! finished) { String::CharPointerType startOfLine (text); int numChars = 0; for (;;) { const juce_wchar c = text.getAndAdvance(); if (c == 0) { finished = true; break; } if (c == '\n') break; if (c == '\r') { if (*text == '\n') ++text; break; } ++numChars; } add (String (startOfLine, numChars)); ++numLines; } return numLines; } void StringArray::removeDuplicates (const bool ignoreCase) { for (int i = 0; i < size() - 1; ++i) { const String s (strings.getReference(i)); int nextIndex = i + 1; for (;;) { nextIndex = indexOf (s, ignoreCase, nextIndex); if (nextIndex < 0) break; strings.remove (nextIndex); } } } void StringArray::appendNumbersToDuplicates (const bool ignoreCase, const bool appendNumberToFirstInstance, CharPointer_UTF8 preNumberString, CharPointer_UTF8 postNumberString) { CharPointer_UTF8 defaultPre (" ("), defaultPost (")"); if (preNumberString.getAddress() == nullptr) preNumberString = defaultPre; if (postNumberString.getAddress() == nullptr) postNumberString = defaultPost; for (int i = 0; i < size() - 1; ++i) { String& s = strings.getReference(i); int nextIndex = indexOf (s, ignoreCase, i + 1); if (nextIndex >= 0) { const String original (s); int number = 0; if (appendNumberToFirstInstance) s = original + String (preNumberString) + String (++number) + String (postNumberString); else ++number; while (nextIndex >= 0) { set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString)); nextIndex = indexOf (original, ignoreCase, nextIndex + 1); } } } } void StringArray::minimiseStorageOverheads() { strings.minimiseStorageOverheads(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringArray.cpp ***/ /*** Start of inlined file: juce_StringPairArray.cpp ***/ BEGIN_JUCE_NAMESPACE StringPairArray::StringPairArray (const bool ignoreCase_) : ignoreCase (ignoreCase_) { } StringPairArray::StringPairArray (const StringPairArray& other) : keys (other.keys), values (other.values), ignoreCase (other.ignoreCase) { } StringPairArray::~StringPairArray() { } StringPairArray& StringPairArray::operator= (const StringPairArray& other) { keys = other.keys; values = other.values; return *this; } bool StringPairArray::operator== (const StringPairArray& other) const { for (int i = keys.size(); --i >= 0;) if (other [keys[i]] != values[i]) return false; return true; } bool StringPairArray::operator!= (const StringPairArray& other) const { return ! operator== (other); } const String& StringPairArray::operator[] (const String& key) const { return values [keys.indexOf (key, ignoreCase)]; } String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const { const int i = keys.indexOf (key, ignoreCase); if (i >= 0) return values[i]; return defaultReturnValue; } void StringPairArray::set (const String& key, const String& value) { const int i = keys.indexOf (key, ignoreCase); if (i >= 0) { values.set (i, value); } else { keys.add (key); values.add (value); } } void StringPairArray::addArray (const StringPairArray& other) { for (int i = 0; i < other.size(); ++i) set (other.keys[i], other.values[i]); } void StringPairArray::clear() { keys.clear(); values.clear(); } void StringPairArray::remove (const String& key) { remove (keys.indexOf (key, ignoreCase)); } void StringPairArray::remove (const int index) { keys.remove (index); values.remove (index); } void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) { ignoreCase = shouldIgnoreCase; } String StringPairArray::getDescription() const { String s; for (int i = 0; i < keys.size(); ++i) { s << keys[i] << " = " << values[i]; if (i < keys.size()) s << ", "; } return s; } void StringPairArray::minimiseStorageOverheads() { keys.minimiseStorageOverheads(); values.minimiseStorageOverheads(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringPairArray.cpp ***/ /*** Start of inlined file: juce_StringPool.cpp ***/ BEGIN_JUCE_NAMESPACE StringPool::StringPool() noexcept {} StringPool::~StringPool() {} namespace StringPoolHelpers { template const String::CharPointerType getPooledStringFromArray (Array& strings, StringType newString) { int start = 0; int end = strings.size(); for (;;) { if (start >= end) { jassert (start <= end); strings.insert (start, newString); return strings.getReference (start).getCharPointer(); } else { const String& startString = strings.getReference (start); if (startString == newString) return startString.getCharPointer(); const int halfway = (start + end) >> 1; if (halfway == start) { if (startString.compare (newString) < 0) ++start; strings.insert (start, newString); return strings.getReference (start).getCharPointer(); } const int comp = strings.getReference (halfway).compare (newString); if (comp == 0) return strings.getReference (halfway).getCharPointer(); else if (comp < 0) start = halfway; else end = halfway; } } } } const String::CharPointerType StringPool::getPooledString (const String& s) { if (s.isEmpty()) return String::empty.getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s); } const String::CharPointerType StringPool::getPooledString (const char* const s) { if (s == nullptr || *s == 0) return String::empty.getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s); } const String::CharPointerType StringPool::getPooledString (const wchar_t* const s) { if (s == nullptr || *s == 0) return String::empty.getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s); } int StringPool::size() const noexcept { return strings.size(); } const String::CharPointerType StringPool::operator[] (const int index) const noexcept { return strings [index].getCharPointer(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_StringPool.cpp ***/ /*** Start of inlined file: juce_XmlDocument.cpp ***/ BEGIN_JUCE_NAMESPACE XmlDocument::XmlDocument (const String& documentText) : originalText (documentText), input (nullptr), ignoreEmptyTextElements (true) { } XmlDocument::XmlDocument (const File& file) : input (nullptr), ignoreEmptyTextElements (true), inputSource (new FileInputSource (file)) { } XmlDocument::~XmlDocument() { } XmlElement* XmlDocument::parse (const File& file) { XmlDocument doc (file); return doc.getDocumentElement(); } XmlElement* XmlDocument::parse (const String& xmlData) { XmlDocument doc (xmlData); return doc.getDocumentElement(); } void XmlDocument::setInputSource (InputSource* const newSource) noexcept { inputSource = newSource; } void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept { ignoreEmptyTextElements = shouldBeIgnored; } namespace XmlIdentifierChars { bool isIdentifierCharSlow (const juce_wchar c) noexcept { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '-' || c == ':' || c == '.'; } bool isIdentifierChar (const juce_wchar c) noexcept { static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) : isIdentifierCharSlow (c); } /*static void generateIdentifierCharConstants() { uint32 n[8] = { 0 }; for (int i = 0; i < 256; ++i) if (isIdentifierCharSlow (i)) n[i >> 5] |= (1 << (i & 31)); String s; for (int i = 0; i < 8; ++i) s << "0x" << String::toHexString ((int) n[i]) << ", "; DBG (s); }*/ } XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) { String textToParse (originalText); if (textToParse.isEmpty() && inputSource != nullptr) { ScopedPointer in (inputSource->createInputStream()); if (in != nullptr) { MemoryOutputStream data; data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); textToParse = data.toString(); if (! onlyReadOuterDocumentElement) originalText = textToParse; } } input = textToParse.getCharPointer(); lastError = String::empty; errorOccurred = false; outOfData = false; needToLoadDTD = true; if (textToParse.isEmpty()) { lastError = "not enough input"; } else { skipHeader(); if (input.getAddress() != nullptr) { ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); if (! errorOccurred) return result.release(); } else { lastError = "incorrect xml header"; } } return nullptr; } const String& XmlDocument::getLastParseError() const noexcept { return lastError; } void XmlDocument::setLastError (const String& desc, const bool carryOn) { lastError = desc; errorOccurred = ! carryOn; } String XmlDocument::getFileContents (const String& filename) const { if (inputSource != nullptr) { const ScopedPointer in (inputSource->createInputStreamFor (filename.trim().unquoted())); if (in != nullptr) return in->readEntireStreamAsString(); } return String::empty; } juce_wchar XmlDocument::readNextChar() noexcept { if (*input != 0) return *input++; outOfData = true; return 0; } int XmlDocument::findNextTokenLength() noexcept { int len = 0; juce_wchar c = *input; while (XmlIdentifierChars::isIdentifierChar (c)) c = input [++len]; return len; } void XmlDocument::skipHeader() { const int headerStart = input.indexOf (CharPointer_UTF8 ("= 0) { const int headerEnd = (input + headerStart).indexOf (CharPointer_UTF8 ("?>")); if (headerEnd < 0) return; #if JUCE_DEBUG const String header (input + headerStart, headerEnd - headerStart); const String encoding (header.fromFirstOccurrenceOf ("encoding", false, true) .fromFirstOccurrenceOf ("=", false, false) .fromFirstOccurrenceOf ("\"", false, false) .upToFirstOccurrenceOf ("\"", false, false).trim()); /* If you load an XML document with a non-UTF encoding type, it may have been loaded wrongly.. Since all the files are read via the normal juce file streams, they're treated as UTF-8, so by the time it gets to the parser, the encoding will have been lost. Best plan is to stick to utf-8 or if you have specific files to read, use your own code to convert them to a unicode String, and pass that to the XML parser. */ jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); #endif input += headerEnd + 2; } skipNextWhiteSpace(); const int docTypeIndex = input.indexOf (CharPointer_UTF8 (" 0) { const juce_wchar c = readNextChar(); if (outOfData) return; if (c == '<') ++n; else if (c == '>') --n; } dtdText = String (docType, (int) (input.getAddress() - (docType.getAddress() + 1))).trim(); } void XmlDocument::skipNextWhiteSpace() { for (;;) { juce_wchar c = *input; while (CharacterFunctions::isWhitespace (c)) c = *++input; if (c == 0) { outOfData = true; break; } else if (c == '<') { if (input[1] == '!' && input[2] == '-' && input[3] == '-') { const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); if (closeComment < 0) { outOfData = true; break; } input += closeComment + 3; continue; } else if (input[1] == '?') { const int closeBracket = input.indexOf (CharPointer_UTF8 ("?>")); if (closeBracket < 0) { outOfData = true; break; } input += closeBracket + 2; continue; } } break; } } void XmlDocument::readQuotedString (String& result) { const juce_wchar quote = readNextChar(); while (! outOfData) { const juce_wchar c = readNextChar(); if (c == quote) break; if (c == '&') { --input; readEntity (result); } else { --input; const String::CharPointerType start (input); for (;;) { const juce_wchar character = *input; if (character == quote) { result.appendCharPointer (start, (int) (input.getAddress() - start.getAddress())); ++input; return; } else if (character == '&') { result.appendCharPointer (start, (int) (input.getAddress() - start.getAddress())); break; } else if (character == 0) { outOfData = true; setLastError ("unmatched quotes", false); break; } ++input; } } } } XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) { XmlElement* node = nullptr; skipNextWhiteSpace(); if (outOfData) return nullptr; const int openBracket = input.indexOf ((juce_wchar) '<'); if (openBracket >= 0) { input += openBracket + 1; int tagLen = findNextTokenLength(); if (tagLen == 0) { // no tag name - but allow for a gap after the '<' before giving an error skipNextWhiteSpace(); tagLen = findNextTokenLength(); if (tagLen == 0) { setLastError ("tag name missing", false); return node; } } node = new XmlElement (String (input, tagLen)); input += tagLen; LinkedListPointer::Appender attributeAppender (node->attributes); // look for attributes for (;;) { skipNextWhiteSpace(); const juce_wchar c = *input; // empty tag.. if (c == '/' && input[1] == '>') { input += 2; break; } // parse the guts of the element.. if (c == '>') { ++input; if (alsoParseSubElements) readChildElements (node); break; } // get an attribute.. if (XmlIdentifierChars::isIdentifierChar (c)) { const int attNameLen = findNextTokenLength(); if (attNameLen > 0) { const String::CharPointerType attNameStart (input); input += attNameLen; skipNextWhiteSpace(); if (readNextChar() == '=') { skipNextWhiteSpace(); const juce_wchar nextChar = *input; if (nextChar == '"' || nextChar == '\'') { XmlElement::XmlAttributeNode* const newAtt = new XmlElement::XmlAttributeNode (String (attNameStart, attNameLen), String::empty); readQuotedString (newAtt->value); attributeAppender.append (newAtt); continue; } } } } else { if (! outOfData) setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); } break; } } return node; } void XmlDocument::readChildElements (XmlElement* parent) { LinkedListPointer::Appender childAppender (parent->firstChildElement); for (;;) { const String::CharPointerType preWhitespaceInput (input); skipNextWhiteSpace(); if (outOfData) { setLastError ("unmatched tags", false); break; } if (*input == '<') { if (input[1] == '/') { // our close tag.. const int closeTag = input.indexOf ((juce_wchar) '>'); if (closeTag >= 0) input += closeTag + 1; break; } else if (input[1] == '!' && input[2] == '[' && input[3] == 'C' && input[4] == 'D' && input[5] == 'A' && input[6] == 'T' && input[7] == 'A' && input[8] == '[') { input += 9; const String::CharPointerType inputStart (input); int len = 0; for (;;) { if (*input == 0) { setLastError ("unterminated CDATA section", false); outOfData = true; break; } else if (input[0] == ']' && input[1] == ']' && input[2] == '>') { input += 3; break; } ++input; ++len; } childAppender.append (XmlElement::createTextElement (String (inputStart, len))); } else { // this is some other element, so parse and add it.. XmlElement* const n = readNextElement (true); if (n != nullptr) childAppender.append (n); else break; } } else // must be a character block { input = preWhitespaceInput; // roll back to include the leading whitespace String textElementContent; for (;;) { const juce_wchar c = *input; if (c == '<') break; if (c == 0) { setLastError ("unmatched tags", false); outOfData = true; return; } if (c == '&') { String entity; readEntity (entity); if (entity.startsWithChar ('<') && entity [1] != 0) { const String::CharPointerType oldInput (input); const bool oldOutOfData = outOfData; input = entity.getCharPointer(); outOfData = false; for (;;) { XmlElement* const n = readNextElement (true); if (n == nullptr) break; childAppender.append (n); } input = oldInput; outOfData = oldOutOfData; } else { textElementContent += entity; } } else { const String::CharPointerType start (input); int len = 0; for (;;) { const juce_wchar nextChar = *input; if (nextChar == '<' || nextChar == '&') { break; } else if (nextChar == 0) { setLastError ("unmatched tags", false); outOfData = true; return; } ++input; ++len; } textElementContent.appendCharPointer (start, len); } } if ((! ignoreEmptyTextElements) || textElementContent.containsNonWhitespaceChars()) { childAppender.append (XmlElement::createTextElement (textElementContent)); } } } } void XmlDocument::readEntity (String& result) { // skip over the ampersand ++input; if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("amp;"), 4) == 0) { input += 4; result += '&'; } else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("quot;"), 5) == 0) { input += 5; result += '"'; } else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("apos;"), 5) == 0) { input += 5; result += '\''; } else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("lt;"), 3) == 0) { input += 3; result += '<'; } else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("gt;"), 3) == 0) { input += 3; result += '>'; } else if (*input == '#') { int charCode = 0; ++input; if (*input == 'x' || *input == 'X') { ++input; int numChars = 0; while (input[0] != ';') { const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); if (hexValue < 0 || ++numChars > 8) { setLastError ("illegal escape sequence", true); break; } charCode = (charCode << 4) | hexValue; ++input; } ++input; } else if (input[0] >= '0' && input[0] <= '9') { int numChars = 0; while (input[0] != ';') { if (++numChars > 12) { setLastError ("illegal escape sequence", true); break; } charCode = charCode * 10 + (input[0] - '0'); ++input; } ++input; } else { setLastError ("illegal escape sequence", true); result += '&'; return; } result << (juce_wchar) charCode; } else { const String::CharPointerType entityNameStart (input); const int closingSemiColon = input.indexOf ((juce_wchar) ';'); if (closingSemiColon < 0) { outOfData = true; result += '&'; } else { input += closingSemiColon + 1; result += expandExternalEntity (String (entityNameStart, closingSemiColon)); } } } String XmlDocument::expandEntity (const String& ent) { if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); if (ent[0] == '#') { const juce_wchar char1 = ent[1]; if (char1 == 'x' || char1 == 'X') return String::charToString (static_cast (ent.substring (2).getHexValue32())); if (char1 >= '0' && char1 <= '9') return String::charToString (static_cast (ent.substring (1).getIntValue())); setLastError ("illegal escape sequence", false); return String::charToString ('&'); } return expandExternalEntity (ent); } String XmlDocument::expandExternalEntity (const String& entity) { if (needToLoadDTD) { if (dtdText.isNotEmpty()) { dtdText = dtdText.trimCharactersAtEnd (">"); tokenisedDTD.addTokens (dtdText, true); if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) { const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); tokenisedDTD.clear(); tokenisedDTD.addTokens (getFileContents (fn), true); } else { tokenisedDTD.clear(); const int openBracket = dtdText.indexOfChar ('['); if (openBracket > 0) { const int closeBracket = dtdText.lastIndexOfChar (']'); if (closeBracket > openBracket) tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, closeBracket), true); } } for (int i = tokenisedDTD.size(); --i >= 0;) { if (tokenisedDTD[i].startsWithChar ('%') && tokenisedDTD[i].endsWithChar (';')) { const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); StringArray newToks; newToks.addTokens (parsed, true); tokenisedDTD.remove (i); for (int j = newToks.size(); --j >= 0;) tokenisedDTD.insert (i, newToks[j]); } } } needToLoadDTD = false; } for (int i = 0; i < tokenisedDTD.size(); ++i) { if (tokenisedDTD[i] == entity) { if (tokenisedDTD[i - 1].equalsIgnoreCase ("").trim().unquoted()); // check for sub-entities.. int ampersand = ent.indexOfChar ('&'); while (ampersand >= 0) { const int semiColon = ent.indexOf (i + 1, ";"); if (semiColon < 0) { setLastError ("entity without terminating semi-colon", false); break; } const String resolved (expandEntity (ent.substring (i + 1, semiColon))); ent = ent.substring (0, ampersand) + resolved + ent.substring (semiColon + 1); ampersand = ent.indexOfChar (semiColon + 1, '&'); } return ent; } } } setLastError ("unknown entity", true); return entity; } String XmlDocument::getParameterEntity (const String& entity) { for (int i = 0; i < tokenisedDTD.size(); ++i) { if (tokenisedDTD[i] == entity && tokenisedDTD [i - 1] == "%" && tokenisedDTD [i - 2].equalsIgnoreCase ("")); if (ent.equalsIgnoreCase ("system")) return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); else return ent.trim().unquoted(); } } return entity; } END_JUCE_NAMESPACE /*** End of inlined file: juce_XmlDocument.cpp ***/ /*** Start of inlined file: juce_XmlElement.cpp ***/ BEGIN_JUCE_NAMESPACE XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept : name (other.name), value (other.value) { } XmlElement::XmlAttributeNode::XmlAttributeNode (const String& name_, const String& value_) noexcept : name (name_), value (value_) { #if JUCE_DEBUG // this checks whether the attribute name string contains any illegal characters.. for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t) jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':'); #endif } inline bool XmlElement::XmlAttributeNode::hasName (const String& nameToMatch) const noexcept { return name.equalsIgnoreCase (nameToMatch); } XmlElement::XmlElement (const String& tagName_) noexcept : tagName (tagName_) { // the tag name mustn't be empty, or it'll look like a text element! jassert (tagName_.containsNonWhitespaceChars()) // The tag can't contain spaces or other characters that would create invalid XML! jassert (! tagName_.containsAnyOf (" <>/&")); } XmlElement::XmlElement (int /*dummy*/) noexcept { } XmlElement::XmlElement (const XmlElement& other) : tagName (other.tagName) { copyChildrenAndAttributesFrom (other); } XmlElement& XmlElement::operator= (const XmlElement& other) { if (this != &other) { removeAllAttributes(); deleteAllChildElements(); tagName = other.tagName; copyChildrenAndAttributesFrom (other); } return *this; } void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) { jassert (firstChildElement.get() == nullptr); firstChildElement.addCopyOfList (other.firstChildElement); jassert (attributes.get() == nullptr); attributes.addCopyOfList (other.attributes); } XmlElement::~XmlElement() noexcept { firstChildElement.deleteAll(); attributes.deleteAll(); } namespace XmlOutputFunctions { /*bool isLegalXmlCharSlow (const juce_wchar character) noexcept { if ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9')) return true; const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; do { if (((juce_wchar) (uint8) *t) == character) return true; } while (*++t != 0); return false; } void generateLegalCharConstants() { uint8 n[32] = { 0 }; for (int i = 0; i < 256; ++i) if (isLegalXmlCharSlow (i)) n[i >> 3] |= (1 << (i & 7)); String s; for (int i = 0; i < 32; ++i) s << (int) n[i] << ", "; DBG (s); }*/ bool isLegalXmlChar (const uint32 c) noexcept { static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 127 }; return c < sizeof (legalChars) * 8 && (legalChars [c >> 3] & (1 << (c & 7))) != 0; } void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) { String::CharPointerType t (text.getCharPointer()); for (;;) { const uint32 character = (uint32) t.getAndAdvance(); if (character == 0) break; if (isLegalXmlChar (character)) { outputStream << (char) character; } else { switch (character) { case '&': outputStream << "&"; break; case '"': outputStream << """; break; case '>': outputStream << ">"; break; case '<': outputStream << "<"; break; case '\n': case '\r': if (! changeNewLines) { outputStream << (char) character; break; } // Note: deliberate fall-through here! default: outputStream << "&#" << ((int) character) << ';'; break; } } } } void writeSpaces (OutputStream& out, const int numSpaces) { out.writeRepeatedByte (' ', numSpaces); } } void XmlElement::writeElementAsText (OutputStream& outputStream, const int indentationLevel, const int lineWrapLength) const { using namespace XmlOutputFunctions; writeSpaces (outputStream, indentationLevel); if (! isTextElement()) { outputStream.writeByte ('<'); outputStream << tagName; { const int attIndent = indentationLevel + tagName.length() + 1; int lineLen = 0; for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) { if (lineLen > lineWrapLength && indentationLevel >= 0) { outputStream << newLine; writeSpaces (outputStream, attIndent); lineLen = 0; } const int64 startPos = outputStream.getPosition(); outputStream.writeByte (' '); outputStream << att->name; outputStream.write ("=\"", 2); escapeIllegalXmlChars (outputStream, att->value, true); outputStream.writeByte ('"'); lineLen += (int) (outputStream.getPosition() - startPos); } } if (firstChildElement != nullptr) { outputStream.writeByte ('>'); XmlElement* child = firstChildElement; bool lastWasTextNode = false; while (child != nullptr) { if (child->isTextElement()) { escapeIllegalXmlChars (outputStream, child->getText(), false); lastWasTextNode = true; } else { if (indentationLevel >= 0 && ! lastWasTextNode) outputStream << newLine; child->writeElementAsText (outputStream, lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength); lastWasTextNode = false; } child = child->getNextElement(); } if (indentationLevel >= 0 && ! lastWasTextNode) { outputStream << newLine; writeSpaces (outputStream, indentationLevel); } outputStream.write ("'); } else { outputStream.write ("/>", 2); } } else { escapeIllegalXmlChars (outputStream, getText(), false); } } String XmlElement::createDocument (const String& dtdToUse, const bool allOnOneLine, const bool includeXmlHeader, const String& encodingType, const int lineWrapLength) const { MemoryOutputStream mem (2048); writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); return mem.toUTF8(); } void XmlElement::writeToStream (OutputStream& output, const String& dtdToUse, const bool allOnOneLine, const bool includeXmlHeader, const String& encodingType, const int lineWrapLength) const { using namespace XmlOutputFunctions; if (includeXmlHeader) { output << ""; if (allOnOneLine) output.writeByte (' '); else output << newLine << newLine; } if (dtdToUse.isNotEmpty()) { output << dtdToUse; if (allOnOneLine) output.writeByte (' '); else output << newLine; } writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); if (! allOnOneLine) output << newLine; } bool XmlElement::writeToFile (const File& file, const String& dtdToUse, const String& encodingType, const int lineWrapLength) const { if (file.hasWriteAccess()) { TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != nullptr) { writeToStream (*out, dtdToUse, false, true, encodingType, lineWrapLength); out = nullptr; return tempFile.overwriteTargetFileWithTemporary(); } } return false; } bool XmlElement::hasTagName (const String& tagNameWanted) const noexcept { #if JUCE_DEBUG // if debugging, check that the case is actually the same, because // valid xml is case-sensitive, and although this lets it pass, it's // better not to.. if (tagName.equalsIgnoreCase (tagNameWanted)) { jassert (tagName == tagNameWanted); return true; } else { return false; } #else return tagName.equalsIgnoreCase (tagNameWanted); #endif } XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const { XmlElement* e = nextListItem; while (e != nullptr && ! e->hasTagName (requiredTagName)) e = e->nextListItem; return e; } int XmlElement::getNumAttributes() const noexcept { return attributes.size(); } const String& XmlElement::getAttributeName (const int index) const noexcept { const XmlAttributeNode* const att = attributes [index]; return att != nullptr ? att->name : String::empty; } const String& XmlElement::getAttributeValue (const int index) const noexcept { const XmlAttributeNode* const att = attributes [index]; return att != nullptr ? att->value : String::empty; } bool XmlElement::hasAttribute (const String& attributeName) const noexcept { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return true; return false; } const String& XmlElement::getStringAttribute (const String& attributeName) const noexcept { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return att->value; return String::empty; } String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return att->value; return defaultReturnValue; } int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return att->value.getIntValue(); return defaultReturnValue; } double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return att->value.getDoubleValue(); return defaultReturnValue; } bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) { if (att->hasName (attributeName)) { juce_wchar firstChar = att->value[0]; if (CharacterFunctions::isWhitespace (firstChar)) firstChar = att->value.trimStart() [0]; return firstChar == '1' || firstChar == 't' || firstChar == 'y' || firstChar == 'T' || firstChar == 'Y'; } } return defaultReturnValue; } bool XmlElement::compareAttribute (const String& attributeName, const String& stringToCompareAgainst, const bool ignoreCase) const noexcept { for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) if (att->hasName (attributeName)) return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) : att->value == stringToCompareAgainst; return false; } void XmlElement::setAttribute (const String& attributeName, const String& value) { if (attributes == nullptr) { attributes = new XmlAttributeNode (attributeName, value); } else { XmlAttributeNode* att = attributes; for (;;) { if (att->hasName (attributeName)) { att->value = value; break; } else if (att->nextListItem == nullptr) { att->nextListItem = new XmlAttributeNode (attributeName, value); break; } att = att->nextListItem; } } } void XmlElement::setAttribute (const String& attributeName, const int number) { setAttribute (attributeName, String (number)); } void XmlElement::setAttribute (const String& attributeName, const double number) { setAttribute (attributeName, String (number)); } void XmlElement::removeAttribute (const String& attributeName) noexcept { LinkedListPointer* att = &attributes; while (att->get() != nullptr) { if (att->get()->hasName (attributeName)) { delete att->removeNext(); break; } att = &(att->get()->nextListItem); } } void XmlElement::removeAllAttributes() noexcept { attributes.deleteAll(); } int XmlElement::getNumChildElements() const noexcept { return firstChildElement.size(); } XmlElement* XmlElement::getChildElement (const int index) const noexcept { return firstChildElement [index].get(); } XmlElement* XmlElement::getChildByName (const String& childName) const noexcept { XmlElement* child = firstChildElement; while (child != nullptr) { if (child->hasTagName (childName)) break; child = child->nextListItem; } return child; } void XmlElement::addChildElement (XmlElement* const newNode) noexcept { if (newNode != nullptr) firstChildElement.append (newNode); } void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept { if (newNode != nullptr) { removeChildElement (newNode, false); firstChildElement.insertAtIndex (indexToInsertAt, newNode); } } XmlElement* XmlElement::createNewChildElement (const String& childTagName) { XmlElement* const newElement = new XmlElement (childTagName); addChildElement (newElement); return newElement; } bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, XmlElement* const newNode) noexcept { if (newNode != nullptr) { LinkedListPointer* const p = firstChildElement.findPointerTo (currentChildElement); if (p != nullptr) { if (currentChildElement != newNode) delete p->replaceNext (newNode); return true; } } return false; } void XmlElement::removeChildElement (XmlElement* const childToRemove, const bool shouldDeleteTheChild) noexcept { if (childToRemove != nullptr) { firstChildElement.remove (childToRemove); if (shouldDeleteTheChild) delete childToRemove; } } bool XmlElement::isEquivalentTo (const XmlElement* const other, const bool ignoreOrderOfAttributes) const noexcept { if (this != other) { if (other == nullptr || tagName != other->tagName) return false; if (ignoreOrderOfAttributes) { int totalAtts = 0; const XmlAttributeNode* att = attributes; while (att != nullptr) { if (! other->compareAttribute (att->name, att->value)) return false; att = att->nextListItem; ++totalAtts; } if (totalAtts != other->getNumAttributes()) return false; } else { const XmlAttributeNode* thisAtt = attributes; const XmlAttributeNode* otherAtt = other->attributes; for (;;) { if (thisAtt == nullptr || otherAtt == nullptr) { if (thisAtt == otherAtt) // both 0, so it's a match break; return false; } if (thisAtt->name != otherAtt->name || thisAtt->value != otherAtt->value) { return false; } thisAtt = thisAtt->nextListItem; otherAtt = otherAtt->nextListItem; } } const XmlElement* thisChild = firstChildElement; const XmlElement* otherChild = other->firstChildElement; for (;;) { if (thisChild == nullptr || otherChild == nullptr) { if (thisChild == otherChild) // both 0, so it's a match break; return false; } if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) return false; thisChild = thisChild->nextListItem; otherChild = otherChild->nextListItem; } } return true; } void XmlElement::deleteAllChildElements() noexcept { firstChildElement.deleteAll(); } void XmlElement::deleteAllChildElementsWithTagName (const String& name) noexcept { XmlElement* child = firstChildElement; while (child != nullptr) { XmlElement* const nextChild = child->nextListItem; if (child->hasTagName (name)) removeChildElement (child, true); child = nextChild; } } bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept { return firstChildElement.contains (possibleChild); } XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept { if (this == elementToLookFor || elementToLookFor == nullptr) return nullptr; XmlElement* child = firstChildElement; while (child != nullptr) { if (elementToLookFor == child) return this; XmlElement* const found = child->findParentElementOf (elementToLookFor); if (found != nullptr) return found; child = child->nextListItem; } return nullptr; } void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept { firstChildElement.copyToArray (elems); } void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept { XmlElement* e = firstChildElement = elems[0]; for (int i = 1; i < num; ++i) { e->nextListItem = elems[i]; e = e->nextListItem; } e->nextListItem = nullptr; } bool XmlElement::isTextElement() const noexcept { return tagName.isEmpty(); } static const String juce_xmltextContentAttributeName ("text"); const String& XmlElement::getText() const noexcept { jassert (isTextElement()); // you're trying to get the text from an element that // isn't actually a text element.. If this contains text sub-nodes, you // probably want to use getAllSubText instead. return getStringAttribute (juce_xmltextContentAttributeName); } void XmlElement::setText (const String& newText) { if (isTextElement()) setAttribute (juce_xmltextContentAttributeName, newText); else jassertfalse; // you can only change the text in a text element, not a normal one. } String XmlElement::getAllSubText() const { if (isTextElement()) return getText(); String result; String::Concatenator concatenator (result); const XmlElement* child = firstChildElement; while (child != nullptr) { concatenator.append (child->getAllSubText()); child = child->nextListItem; } return result; } String XmlElement::getChildElementAllSubText (const String& childTagName, const String& defaultReturnValue) const { const XmlElement* const child = getChildByName (childTagName); if (child != nullptr) return child->getAllSubText(); return defaultReturnValue; } XmlElement* XmlElement::createTextElement (const String& text) { XmlElement* const e = new XmlElement ((int) 0); e->setAttribute (juce_xmltextContentAttributeName, text); return e; } void XmlElement::addTextElement (const String& text) { addChildElement (createTextElement (text)); } void XmlElement::deleteAllTextElements() noexcept { XmlElement* child = firstChildElement; while (child != nullptr) { XmlElement* const next = child->nextListItem; if (child->isTextElement()) removeChildElement (child, true); child = next; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_XmlElement.cpp ***/ /*** Start of inlined file: juce_JSON.cpp ***/ BEGIN_JUCE_NAMESPACE class JSONParser { public: static Result parseAny (String::CharPointerType& t, var& result) { t = t.findEndOfWhitespace(); String::CharPointerType t2 (t); switch (t2.getAndAdvance()) { case '{': t = t2; return parseObject (t, result); case '[': t = t2; return parseArray (t, result); case '"': t = t2; return parseString (t, result); case '-': t2 = t2.findEndOfWhitespace(); if (! CharacterFunctions::isDigit (*t2)) break; t = t2; return parseNumber (t, result, true); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parseNumber (t, result, false); case 't': // "true" if (t2.getAndAdvance() == 'r' && t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'e') { t = t2; result = var (true); return Result::ok(); } break; case 'f': // "false" if (t2.getAndAdvance() == 'a' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 's' && t2.getAndAdvance() == 'e') { t = t2; result = var (false); return Result::ok(); } break; case 'n': // "null" if (t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 'l') { t = t2; result = var::null; return Result::ok(); } break; default: break; } return createFail ("Syntax error", &t); } private: static Result createFail (const char* const message, const String::CharPointerType* location = nullptr) { String m (message); if (location != nullptr) m << ": \"" << String (*location, 20) << '"'; return Result::fail (m); } static Result parseNumber (String::CharPointerType& t, var& result, const bool isNegative) { String::CharPointerType oldT (t); int64 intValue = t.getAndAdvance() - '0'; jassert (intValue >= 0 && intValue < 10); for (;;) { String::CharPointerType previousChar (t); const juce_wchar c = t.getAndAdvance(); const int digit = ((int) c) - '0'; if (isPositiveAndBelow (digit, 10)) { intValue = intValue * 10 + digit; continue; } if (c == 'e' || c == 'E' || c == '.') { t = oldT; const double asDouble = CharacterFunctions::readDoubleValue (t); result = isNegative ? -asDouble : asDouble; return Result::ok(); } if (CharacterFunctions::isWhitespace (c) || c == ',' || c == '}' || c == ']' || c == 0) { t = previousChar; break; } return createFail ("Syntax error in number", &oldT); } const int64 correctedValue = isNegative ? -intValue : intValue; if ((intValue >> 31) != 0) result = correctedValue; else result = (int) correctedValue; return Result::ok(); } static Result parseObject (String::CharPointerType& t, var& result) { DynamicObject* const resultObject = new DynamicObject(); result = resultObject; NamedValueSet& resultProperties = resultObject->getProperties(); for (;;) { t = t.findEndOfWhitespace(); String::CharPointerType oldT (t); const juce_wchar c = t.getAndAdvance(); if (c == '}') break; if (c == 0) return createFail ("Unexpected end-of-input in object declaration"); if (c == '"') { var propertyNameVar; Result r (parseString (t, propertyNameVar)); if (r.failed()) return r; const String propertyName (propertyNameVar.toString()); if (propertyName.isNotEmpty()) { t = t.findEndOfWhitespace(); oldT = t; const juce_wchar c2 = t.getAndAdvance(); if (c2 != ':') return createFail ("Expected ':', but found", &oldT); resultProperties.set (propertyName, var::null); var* propertyValue = resultProperties.getVarPointer (propertyName); Result r2 (parseAny (t, *propertyValue)); if (r2.failed()) return r2; t = t.findEndOfWhitespace(); oldT = t; const juce_wchar nextChar = t.getAndAdvance(); if (nextChar == ',') continue; else if (nextChar == '}') break; } } return createFail ("Expected object member declaration, but found", &oldT); } return Result::ok(); } static Result parseArray (String::CharPointerType& t, var& result) { result = var (Array()); Array* const destArray = result.getArray(); for (;;) { t = t.findEndOfWhitespace(); String::CharPointerType oldT (t); const juce_wchar c = t.getAndAdvance(); if (c == ']') break; if (c == 0) return createFail ("Unexpected end-of-input in array declaration"); t = oldT; destArray->add (var::null); Result r (parseAny (t, destArray->getReference (destArray->size() - 1))); if (r.failed()) return r; t = t.findEndOfWhitespace(); oldT = t; const juce_wchar nextChar = t.getAndAdvance(); if (nextChar == ',') continue; else if (nextChar == ']') break; return createFail ("Expected object array item, but found", &oldT); } return Result::ok(); } static Result parseString (String::CharPointerType& t, var& result) { Array buffer; buffer.ensureStorageAllocated (256); for (;;) { juce_wchar c = t.getAndAdvance(); if (c == '"') break; if (c == '\\') { c = t.getAndAdvance(); switch (c) { case '"': case '\\': case '/': break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'u': { c = 0; for (int i = 4; --i >= 0;) { const int digitValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); if (digitValue < 0) return createFail ("Syntax error in unicode escape sequence"); c = (juce_wchar) ((c << 4) + digitValue); } break; } } } if (c == 0) return createFail ("Unexpected end-of-input in string constant"); buffer.add (c); } buffer.add (0); result = String (CharPointer_UTF32 (buffer.getRawDataPointer())); return Result::ok(); } }; class JSONFormatter { public: static void write (OutputStream& out, const var& v, const int indentLevel, const bool allOnOneLine) { if (v.isString()) { writeString (out, v.toString().getCharPointer()); } else if (v.isVoid()) { out << "null"; } else if (v.isBool()) { out << (static_cast (v) ? "true" : "false"); } else if (v.isArray()) { writeArray (out, *v.getArray(), indentLevel, allOnOneLine); } else if (v.isObject()) { DynamicObject* object = dynamic_cast (v.getObject()); jassert (object != nullptr); // Only DynamicObjects can be converted to JSON! writeObject (out, *object, indentLevel, allOnOneLine); } else { jassert (! v.isMethod()); // Can't convert an object with methods to JSON! out << v.toString(); } } private: enum { indentSize = 2 }; static void writeEscapedChar (OutputStream& out, const unsigned short value) { out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4); } static void writeString (OutputStream& out, String::CharPointerType t) { out << '"'; for (;;) { const juce_wchar c (t.getAndAdvance()); switch (c) { case 0: out << '"'; return; case '\"': out << "\\\""; break; case '\\': out << "\\\\"; break; case '\b': out << "\\b"; break; case '\f': out << "\\f"; break; case '\t': out << "\\t"; break; case '\r': out << "\\r"; break; case '\n': out << "\\n"; break; default: if (c >= 32 && c < 127) { out << (char) c; } else { if (CharPointer_UTF16::getBytesRequiredFor (c) > 2) { CharPointer_UTF16::CharType chars[2]; CharPointer_UTF16 utf16 (chars); utf16.write (c); for (int i = 0; i < 2; ++i) writeEscapedChar (out, (unsigned short) chars[i]); } else { writeEscapedChar (out, (unsigned short) c); } } break; } } } static void writeSpaces (OutputStream& out, int numSpaces) { out.writeRepeatedByte (' ', numSpaces); } static void writeArray (OutputStream& out, const Array& array, const int indentLevel, const bool allOnOneLine) { out << '['; if (! allOnOneLine) out << newLine; for (int i = 0; i < array.size(); ++i) { if (! allOnOneLine) writeSpaces (out, indentLevel + indentSize); write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine); if (i < array.size() - 1) { if (allOnOneLine) out << ", "; else out << ',' << newLine; } else if (! allOnOneLine) out << newLine; } if (! allOnOneLine) writeSpaces (out, indentLevel); out << ']'; } static void writeObject (OutputStream& out, DynamicObject& object, const int indentLevel, const bool allOnOneLine) { NamedValueSet& props = object.getProperties(); out << '{'; if (! allOnOneLine) out << newLine; LinkedListPointer* i = &(props.values); for (;;) { NamedValueSet::NamedValue* const v = i->get(); if (v == nullptr) break; if (! allOnOneLine) writeSpaces (out, indentLevel + indentSize); writeString (out, v->name); out << ": "; write (out, v->value, indentLevel + indentSize, allOnOneLine); if (v->nextListItem.get() != nullptr) { if (allOnOneLine) out << ", "; else out << ',' << newLine; } else if (! allOnOneLine) out << newLine; i = &(v->nextListItem); } if (! allOnOneLine) writeSpaces (out, indentLevel); out << '}'; } }; var JSON::parse (const String& text) { var result; String::CharPointerType t (text.getCharPointer()); if (! JSONParser::parseAny (t, result)) result = var::null; return result; } var JSON::parse (InputStream& input) { return parse (input.readEntireStreamAsString()); } var JSON::parse (const File& file) { return parse (file.loadFileAsString()); } Result JSON::parse (const String& text, var& result) { String::CharPointerType t (text.getCharPointer()); return JSONParser::parseAny (t, result); } String JSON::toString (const var& data, const bool allOnOneLine) { MemoryOutputStream mo (1024); JSONFormatter::write (mo, data, 0, allOnOneLine); return mo.toString(); } void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine) { JSONFormatter::write (output, data, 0, allOnOneLine); } #if JUCE_UNIT_TESTS class JSONTests : public UnitTest { public: JSONTests() : UnitTest ("JSON") {} static String createRandomWideCharString (Random& r) { juce_wchar buffer[40] = { 0 }; for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) { if (r.nextBool()) { do { buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); } while (! CharPointer_UTF16::canRepresent (buffer[i])); } else buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); } return CharPointer_UTF32 (buffer); } static String createRandomIdentifier (Random& r) { char buffer[30] = { 0 }; for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) { static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"; buffer[i] = chars [r.nextInt (sizeof (chars) - 1)]; } return CharPointer_ASCII (buffer); } static var createRandomVar (Random& r, int depth) { switch (r.nextInt (depth > 3 ? 6 : 8)) { case 0: return var::null; case 1: return r.nextInt(); case 2: return r.nextInt64(); case 3: return r.nextBool(); case 4: return r.nextDouble(); case 5: return createRandomWideCharString (r); case 6: { var v (createRandomVar (r, depth + 1)); for (int i = 1 + r.nextInt (30); --i >= 0;) v.append (createRandomVar (r, depth + 1)); return v; } case 7: { DynamicObject* o = new DynamicObject(); for (int i = r.nextInt (30); --i >= 0;) o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1)); return o; } default: return var::null; } } void runTest() { beginTest ("JSON"); Random r; r.setSeedRandomly(); expect (JSON::parse (String::empty) == var::null); expect (JSON::parse ("{}").isObject()); expect (JSON::parse ("[]").isArray()); expect (JSON::parse ("1234").isInt()); expect (JSON::parse ("12345678901234").isInt64()); expect (JSON::parse ("1.123e3").isDouble()); expect (JSON::parse ("-1234").isInt()); expect (JSON::parse ("-12345678901234").isInt64()); expect (JSON::parse ("-1.123e3").isDouble()); for (int i = 100; --i >= 0;) { var v; if (i > 0) v = createRandomVar (r, 0); const bool oneLine = r.nextBool(); String asString (JSON::toString (v, oneLine)); var parsed = JSON::parse (asString); String parsedString (JSON::toString (parsed, oneLine)); expect (asString.isNotEmpty() && parsedString == asString); } } }; static JSONTests JSONUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_JSON.cpp ***/ /*** Start of inlined file: juce_ReadWriteLock.cpp ***/ BEGIN_JUCE_NAMESPACE ReadWriteLock::ReadWriteLock() noexcept : numWaitingWriters (0), numWriters (0), writerThreadId (0) { } ReadWriteLock::~ReadWriteLock() noexcept { jassert (readerThreads.size() == 0); jassert (numWriters == 0); } void ReadWriteLock::enterRead() const noexcept { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const SpinLock::ScopedLockType sl (accessLock); for (;;) { jassert (readerThreads.size() % 2 == 0); int i; for (i = 0; i < readerThreads.size(); i += 2) if (readerThreads.getUnchecked(i) == threadId) break; if (i < readerThreads.size() || numWriters + numWaitingWriters == 0 || (threadId == writerThreadId && numWriters > 0)) { if (i < readerThreads.size()) { readerThreads.set (i + 1, (Thread::ThreadID) (1 + (pointer_sized_int) readerThreads.getUnchecked (i + 1))); } else { readerThreads.add (threadId); readerThreads.add ((Thread::ThreadID) 1); } return; } const SpinLock::ScopedUnlockType ul (accessLock); waitEvent.wait (100); } } void ReadWriteLock::exitRead() const noexcept { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const SpinLock::ScopedLockType sl (accessLock); for (int i = 0; i < readerThreads.size(); i += 2) { if (readerThreads.getUnchecked(i) == threadId) { const pointer_sized_int newCount = ((pointer_sized_int) readerThreads.getUnchecked (i + 1)) - 1; if (newCount == 0) { readerThreads.removeRange (i, 2); waitEvent.signal(); } else { readerThreads.set (i + 1, (Thread::ThreadID) newCount); } return; } } jassertfalse; // unlocking a lock that wasn't locked.. } void ReadWriteLock::enterWrite() const noexcept { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const SpinLock::ScopedLockType sl (accessLock); for (;;) { if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId || (readerThreads.size() == 2 && readerThreads.getUnchecked(0) == threadId)) { writerThreadId = threadId; ++numWriters; break; } ++numWaitingWriters; accessLock.exit(); waitEvent.wait (100); accessLock.enter(); --numWaitingWriters; } } bool ReadWriteLock::tryEnterWrite() const noexcept { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const SpinLock::ScopedLockType sl (accessLock); if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId || (readerThreads.size() == 2 && readerThreads.getUnchecked(0) == threadId)) { writerThreadId = threadId; ++numWriters; return true; } return false; } void ReadWriteLock::exitWrite() const noexcept { const SpinLock::ScopedLockType sl (accessLock); // check this thread actually had the lock.. jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); if (--numWriters == 0) { writerThreadId = 0; waitEvent.signal(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ReadWriteLock.cpp ***/ /*** Start of inlined file: juce_Thread.cpp ***/ BEGIN_JUCE_NAMESPACE class RunningThreadsList { public: RunningThreadsList() { } ~RunningThreadsList() { // Some threads are still running! Make sure you stop all your // threads cleanly before your app quits! jassert (threads.size() == 0); } void add (Thread* const thread) { const SpinLock::ScopedLockType sl (lock); jassert (! threads.contains (thread)); threads.add (thread); } void remove (Thread* const thread) { const SpinLock::ScopedLockType sl (lock); jassert (threads.contains (thread)); threads.removeValue (thread); } int size() const noexcept { return threads.size(); } Thread* getThreadWithID (const Thread::ThreadID targetID) const noexcept { const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) { Thread* const t = threads.getUnchecked(i); if (t->getThreadId() == targetID) return t; } return nullptr; } void stopAll (const int timeOutMilliseconds) { signalAllThreadsToStop(); for (;;) { Thread* firstThread = getFirstThread(); if (firstThread != nullptr) firstThread->stopThread (timeOutMilliseconds); else break; } } static RunningThreadsList& getInstance() { static RunningThreadsList runningThreads; return runningThreads; } private: Array threads; SpinLock lock; void signalAllThreadsToStop() { const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); } Thread* getFirstThread() const { const SpinLock::ScopedLockType sl (lock); return threads.getFirst(); } }; void Thread::threadEntryPoint() { RunningThreadsList::getInstance().add (this); JUCE_TRY { if (threadName_.isNotEmpty()) setCurrentThreadName (threadName_); if (startSuspensionEvent_.wait (10000)) { jassert (getCurrentThreadId() == threadId_); if (affinityMask_ != 0) setCurrentThreadAffinityMask (affinityMask_); run(); } } JUCE_CATCH_ALL_ASSERT RunningThreadsList::getInstance().remove (this); closeThreadHandle(); } // used to wrap the incoming call from the platform-specific code void JUCE_API juce_threadEntryPoint (void* userData) { static_cast (userData)->threadEntryPoint(); } Thread::Thread (const String& threadName) : threadName_ (threadName), threadHandle_ (nullptr), threadId_ (0), threadPriority_ (5), affinityMask_ (0), threadShouldExit_ (false) { } Thread::~Thread() { /* If your thread class's destructor has been called without first stopping the thread, that means that this partially destructed object is still performing some work - and that's probably a Bad Thing! To avoid this type of nastiness, always make sure you call stopThread() before or during your subclass's destructor. */ jassert (! isThreadRunning()); stopThread (100); } void Thread::startThread() { const ScopedLock sl (startStopLock); threadShouldExit_ = false; if (threadHandle_ == nullptr) { launchThread(); setThreadPriority (threadHandle_, threadPriority_); startSuspensionEvent_.signal(); } } void Thread::startThread (const int priority) { const ScopedLock sl (startStopLock); if (threadHandle_ == nullptr) { threadPriority_ = priority; startThread(); } else { setPriority (priority); } } bool Thread::isThreadRunning() const { return threadHandle_ != nullptr; } void Thread::signalThreadShouldExit() { threadShouldExit_ = true; } bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const { // Doh! So how exactly do you expect this thread to wait for itself to stop?? jassert (getThreadId() != getCurrentThreadId()); const int sleepMsPerIteration = 5; int count = timeOutMilliseconds / sleepMsPerIteration; while (isThreadRunning()) { if (timeOutMilliseconds > 0 && --count < 0) return false; sleep (sleepMsPerIteration); } return true; } void Thread::stopThread (const int timeOutMilliseconds) { // agh! You can't stop the thread that's calling this method! How on earth // would that work?? jassert (getCurrentThreadId() != getThreadId()); const ScopedLock sl (startStopLock); if (isThreadRunning()) { signalThreadShouldExit(); notify(); if (timeOutMilliseconds != 0) waitForThreadToExit (timeOutMilliseconds); if (isThreadRunning()) { // very bad karma if this point is reached, as there are bound to be // locks and events left in silly states when a thread is killed by force.. jassertfalse; Logger::writeToLog ("!! killing thread by force !!"); killThread(); RunningThreadsList::getInstance().remove (this); threadHandle_ = nullptr; threadId_ = 0; } } } bool Thread::setPriority (const int priority) { const ScopedLock sl (startStopLock); if (setThreadPriority (threadHandle_, priority)) { threadPriority_ = priority; return true; } return false; } bool Thread::setCurrentThreadPriority (const int priority) { return setThreadPriority (0, priority); } void Thread::setAffinityMask (const uint32 affinityMask) { affinityMask_ = affinityMask; } bool Thread::wait (const int timeOutMilliseconds) const { return defaultEvent_.wait (timeOutMilliseconds); } void Thread::notify() const { defaultEvent_.signal(); } int Thread::getNumRunningThreads() { return RunningThreadsList::getInstance().size(); } Thread* Thread::getCurrentThread() { return RunningThreadsList::getInstance().getThreadWithID (getCurrentThreadId()); } void Thread::stopAllThreads (const int timeOutMilliseconds) { RunningThreadsList::getInstance().stopAll (timeOutMilliseconds); } void SpinLock::enter() const noexcept { if (! tryEnter()) { for (int i = 20; --i >= 0;) if (tryEnter()) return; while (! tryEnter()) Thread::yield(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Thread.cpp ***/ /*** Start of inlined file: juce_ThreadPool.cpp ***/ BEGIN_JUCE_NAMESPACE ThreadPoolJob::ThreadPoolJob (const String& name) : jobName (name), pool (nullptr), shouldStop (false), isActive (false), shouldBeDeleted (false) { } ThreadPoolJob::~ThreadPoolJob() { // you mustn't delete a job while it's still in a pool! Use ThreadPool::removeJob() // to remove it first! jassert (pool == nullptr || ! pool->contains (this)); } String ThreadPoolJob::getJobName() const { return jobName; } void ThreadPoolJob::setJobName (const String& newName) { jobName = newName; } void ThreadPoolJob::signalJobShouldExit() { shouldStop = true; } class ThreadPool::ThreadPoolThread : public Thread { public: ThreadPoolThread (ThreadPool& pool_) : Thread ("Pool"), pool (pool_), busy (false) { } void run() { while (! threadShouldExit()) { if (! pool.runNextJob()) wait (500); } } private: ThreadPool& pool; bool volatile busy; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread); }; ThreadPool::ThreadPool (const int numThreads, const bool startThreadsOnlyWhenNeeded, const int stopThreadsWhenNotUsedTimeoutMs) : threadStopTimeout (stopThreadsWhenNotUsedTimeoutMs), priority (5) { jassert (numThreads > 0); // not much point having one of these with no threads in it. for (int i = jmax (1, numThreads); --i >= 0;) threads.add (new ThreadPoolThread (*this)); if (! startThreadsOnlyWhenNeeded) for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->startThread (priority); } ThreadPool::~ThreadPool() { removeAllJobs (true, 4000); int i; for (i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); for (i = threads.size(); --i >= 0;) threads.getUnchecked(i)->stopThread (500); } void ThreadPool::addJob (ThreadPoolJob* const job) { jassert (job != nullptr); jassert (job->pool == nullptr); if (job->pool == nullptr) { job->pool = this; job->shouldStop = false; job->isActive = false; { const ScopedLock sl (lock); jobs.add (job); int numRunning = 0; for (int i = threads.size(); --i >= 0;) if (threads.getUnchecked(i)->isThreadRunning() && ! threads.getUnchecked(i)->threadShouldExit()) ++numRunning; if (numRunning < threads.size()) { bool startedOne = false; int n = 1000; while (--n >= 0 && ! startedOne) { for (int i = threads.size(); --i >= 0;) { if (! threads.getUnchecked(i)->isThreadRunning()) { threads.getUnchecked(i)->startThread (priority); startedOne = true; break; } } if (! startedOne) Thread::sleep (2); } } } for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->notify(); } } int ThreadPool::getNumJobs() const { return jobs.size(); } ThreadPoolJob* ThreadPool::getJob (const int index) const { const ScopedLock sl (lock); return jobs [index]; } bool ThreadPool::contains (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)); } bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)) && job->isActive; } bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const { if (job != nullptr) { const uint32 start = Time::getMillisecondCounter(); while (contains (job)) { if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (2); } } return true; } bool ThreadPool::removeJob (ThreadPoolJob* const job, const bool interruptIfRunning, const int timeOutMs) { bool dontWait = true; if (job != nullptr) { const ScopedLock sl (lock); if (jobs.contains (job)) { if (job->isActive) { if (interruptIfRunning) job->signalJobShouldExit(); dontWait = false; } else { jobs.removeValue (job); job->pool = nullptr; } } } return dontWait || waitForJobToFinish (job, timeOutMs); } bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, const bool deleteInactiveJobs, ThreadPool::JobSelector* selectedJobsToRemove) { Array jobsToWaitFor; { const ScopedLock sl (lock); for (int i = jobs.size(); --i >= 0;) { ThreadPoolJob* const job = jobs.getUnchecked(i); if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) { if (job->isActive) { jobsToWaitFor.add (job); if (interruptRunningJobs) job->signalJobShouldExit(); } else { jobs.remove (i); if (deleteInactiveJobs) delete job; else job->pool = nullptr; } } } } const uint32 start = Time::getMillisecondCounter(); for (;;) { for (int i = jobsToWaitFor.size(); --i >= 0;) if (! isJobRunning (jobsToWaitFor.getUnchecked (i))) jobsToWaitFor.remove (i); if (jobsToWaitFor.size() == 0) break; if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (20); } return true; } StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) const { StringArray s; const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { const ThreadPoolJob* const job = jobs.getUnchecked(i); if (job->isActive || ! onlyReturnActiveJobs) s.add (job->getJobName()); } return s; } bool ThreadPool::setThreadPriorities (const int newPriority) { bool ok = true; if (priority != newPriority) { priority = newPriority; for (int i = threads.size(); --i >= 0;) if (! threads.getUnchecked(i)->setPriority (newPriority)) ok = false; } return ok; } bool ThreadPool::runNextJob() { ThreadPoolJob* job = nullptr; { const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { job = jobs[i]; if (job != nullptr && ! (job->isActive || job->shouldStop)) break; job = nullptr; } if (job != nullptr) job->isActive = true; } if (job != nullptr) { JUCE_TRY { ThreadPoolJob::JobStatus result = job->runJob(); lastJobEndTime = Time::getApproximateMillisecondCounter(); const ScopedLock sl (lock); if (jobs.contains (job)) { job->isActive = false; if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) { job->pool = nullptr; job->shouldStop = true; jobs.removeValue (job); if (result == ThreadPoolJob::jobHasFinishedAndShouldBeDeleted) delete job; jobFinishedSignal.signal(); } else { // move the job to the end of the queue if it wants another go jobs.move (jobs.indexOf (job), -1); } } } #if JUCE_CATCH_UNHANDLED_EXCEPTIONS catch (...) { const ScopedLock sl (lock); jobs.removeValue (job); } #endif } else { if (threadStopTimeout > 0 && Time::getApproximateMillisecondCounter() > lastJobEndTime + threadStopTimeout) { const ScopedLock sl (lock); if (jobs.size() == 0) for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); } else { return false; } } return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ThreadPool.cpp ***/ /*** Start of inlined file: juce_TimeSliceThread.cpp ***/ BEGIN_JUCE_NAMESPACE TimeSliceThread::TimeSliceThread (const String& threadName) : Thread (threadName), clientBeingCalled (nullptr) { } TimeSliceThread::~TimeSliceThread() { stopThread (2000); } void TimeSliceThread::addTimeSliceClient (TimeSliceClient* const client, int millisecondsBeforeStarting) { if (client != nullptr) { const ScopedLock sl (listLock); client->nextCallTime = Time::getCurrentTime() + RelativeTime::milliseconds (millisecondsBeforeStarting); clients.addIfNotAlreadyThere (client); notify(); } } void TimeSliceThread::removeTimeSliceClient (TimeSliceClient* const client) { const ScopedLock sl1 (listLock); // if there's a chance we're in the middle of calling this client, we need to // also lock the outer lock.. if (clientBeingCalled == client) { const ScopedUnlock ul (listLock); // unlock first to get the order right.. const ScopedLock sl2 (callbackLock); const ScopedLock sl3 (listLock); clients.removeValue (client); } else { clients.removeValue (client); } } int TimeSliceThread::getNumClients() const { return clients.size(); } TimeSliceClient* TimeSliceThread::getClient (const int i) const { const ScopedLock sl (listLock); return clients [i]; } TimeSliceClient* TimeSliceThread::getNextClient (int index) const { Time soonest; TimeSliceClient* client = nullptr; for (int i = clients.size(); --i >= 0;) { TimeSliceClient* const c = clients.getUnchecked ((i + index) % clients.size()); if (client == nullptr || c->nextCallTime < soonest) { client = c; soonest = c->nextCallTime; } } return client; } void TimeSliceThread::run() { int index = 0; while (! threadShouldExit()) { int timeToWait = 500; { Time nextClientTime; { const ScopedLock sl2 (listLock); index = clients.size() > 0 ? ((index + 1) % clients.size()) : 0; TimeSliceClient* const firstClient = getNextClient (index); if (firstClient != nullptr) nextClientTime = firstClient->nextCallTime; } const Time now (Time::getCurrentTime()); if (nextClientTime > now) { timeToWait = (int) jmin ((int64) 500, (nextClientTime - now).inMilliseconds()); } else { timeToWait = index == 0 ? 1 : 0; const ScopedLock sl (callbackLock); { const ScopedLock sl2 (listLock); clientBeingCalled = getNextClient (index); } if (clientBeingCalled != nullptr) { const int msUntilNextCall = clientBeingCalled->useTimeSlice(); const ScopedLock sl2 (listLock); if (msUntilNextCall >= 0) clientBeingCalled->nextCallTime += RelativeTime::milliseconds (msUntilNextCall); else clients.removeValue (clientBeingCalled); clientBeingCalled = nullptr; } } } if (timeToWait > 0) wait (timeToWait); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_TimeSliceThread.cpp ***/ #endif #if JUCE_BUILD_MISC /*** Start of inlined file: juce_ValueTree.cpp ***/ BEGIN_JUCE_NAMESPACE class ValueTree::SetPropertyAction : public UndoableAction { public: SetPropertyAction (const SharedObjectPtr& target_, const Identifier& name_, const var& newValue_, const var& oldValue_, const bool isAddingNewProperty_, const bool isDeletingProperty_) : target (target_), name (name_), newValue (newValue_), oldValue (oldValue_), isAddingNewProperty (isAddingNewProperty_), isDeletingProperty (isDeletingProperty_) { } bool perform() { jassert (! (isAddingNewProperty && target->hasProperty (name))); if (isDeletingProperty) target->removeProperty (name, nullptr); else target->setProperty (name, newValue, nullptr); return true; } bool undo() { if (isAddingNewProperty) target->removeProperty (name, nullptr); else target->setProperty (name, oldValue, nullptr); return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) { if (! (isAddingNewProperty || isDeletingProperty)) { SetPropertyAction* next = dynamic_cast (nextAction); if (next != nullptr && next->target == target && next->name == name && ! (next->isAddingNewProperty || next->isDeletingProperty)) { return new SetPropertyAction (target, name, next->newValue, oldValue, false, false); } } return nullptr; } private: const SharedObjectPtr target; const Identifier name; const var newValue; var oldValue; const bool isAddingNewProperty : 1, isDeletingProperty : 1; JUCE_DECLARE_NON_COPYABLE (SetPropertyAction); }; class ValueTree::AddOrRemoveChildAction : public UndoableAction { public: AddOrRemoveChildAction (const SharedObjectPtr& target_, const int childIndex_, const SharedObjectPtr& newChild_) : target (target_), child (newChild_ != nullptr ? newChild_ : target_->children [childIndex_]), childIndex (childIndex_), isDeleting (newChild_ == nullptr) { jassert (child != nullptr); } bool perform() { if (isDeleting) target->removeChild (childIndex, nullptr); else target->addChild (child, childIndex, nullptr); return true; } bool undo() { if (isDeleting) { target->addChild (child, childIndex, nullptr); } else { // If you hit this, it seems that your object's state is getting confused - probably // because you've interleaved some undoable and non-undoable operations? jassert (childIndex < target->children.size()); target->removeChild (childIndex, nullptr); } return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } private: const SharedObjectPtr target, child; const int childIndex; const bool isDeleting; JUCE_DECLARE_NON_COPYABLE (AddOrRemoveChildAction); }; class ValueTree::MoveChildAction : public UndoableAction { public: MoveChildAction (const SharedObjectPtr& parent_, const int startIndex_, const int endIndex_) : parent (parent_), startIndex (startIndex_), endIndex (endIndex_) { } bool perform() { parent->moveChild (startIndex, endIndex, nullptr); return true; } bool undo() { parent->moveChild (endIndex, startIndex, nullptr); return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) { MoveChildAction* next = dynamic_cast (nextAction); if (next != nullptr && next->parent == parent && next->startIndex == endIndex) return new MoveChildAction (parent, startIndex, next->endIndex); return nullptr; } private: const SharedObjectPtr parent; const int startIndex, endIndex; JUCE_DECLARE_NON_COPYABLE (MoveChildAction); }; ValueTree::SharedObject::SharedObject (const Identifier& type_) : type (type_), parent (nullptr) { } ValueTree::SharedObject::SharedObject (const SharedObject& other) : type (other.type), properties (other.properties), parent (nullptr) { for (int i = 0; i < other.children.size(); ++i) { SharedObject* const child = new SharedObject (*other.children.getUnchecked(i)); child->parent = this; children.add (child); } } ValueTree::SharedObject::~SharedObject() { jassert (parent == nullptr); // this should never happen unless something isn't obeying the ref-counting! for (int i = children.size(); --i >= 0;) { const SharedObjectPtr c (children.getUnchecked(i)); c->parent = nullptr; children.remove (i); c->sendParentChangeMessage(); } } void ValueTree::SharedObject::sendPropertyChangeMessage (ValueTree& tree, const Identifier& property) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != nullptr) v->listeners.call (&ValueTree::Listener::valueTreePropertyChanged, tree, property); } } void ValueTree::SharedObject::sendPropertyChangeMessage (const Identifier& property) { ValueTree tree (this); for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) t->sendPropertyChangeMessage (tree, property); } void ValueTree::SharedObject::sendChildAddedMessage (ValueTree& tree, ValueTree& child) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != nullptr) v->listeners.call (&ValueTree::Listener::valueTreeChildAdded, tree, child); } } void ValueTree::SharedObject::sendChildAddedMessage (ValueTree child) { ValueTree tree (this); for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) t->sendChildAddedMessage (tree, child); } void ValueTree::SharedObject::sendChildRemovedMessage (ValueTree& tree, ValueTree& child) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != nullptr) v->listeners.call (&ValueTree::Listener::valueTreeChildRemoved, tree, child); } } void ValueTree::SharedObject::sendChildRemovedMessage (ValueTree child) { ValueTree tree (this); for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) t->sendChildRemovedMessage (tree, child); } void ValueTree::SharedObject::sendChildOrderChangedMessage (ValueTree& tree) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != nullptr) v->listeners.call (&ValueTree::Listener::valueTreeChildOrderChanged, tree); } } void ValueTree::SharedObject::sendChildOrderChangedMessage() { ValueTree tree (this); for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) t->sendChildOrderChangedMessage (tree); } void ValueTree::SharedObject::sendParentChangeMessage() { ValueTree tree (this); int i; for (i = children.size(); --i >= 0;) { SharedObject* const t = children[i]; if (t != nullptr) t->sendParentChangeMessage(); } for (i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != nullptr) v->listeners.call (&ValueTree::Listener::valueTreeParentChanged, tree); } } const var& ValueTree::SharedObject::getProperty (const Identifier& name) const { return properties [name]; } const var ValueTree::SharedObject::getProperty (const Identifier& name, const var& defaultReturnValue) const { return properties.getWithDefault (name, defaultReturnValue); } void ValueTree::SharedObject::setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) { if (undoManager == nullptr) { if (properties.set (name, newValue)) sendPropertyChangeMessage (name); } else { const var* const existingValue = properties.getVarPointer (name); if (existingValue != nullptr) { if (*existingValue != newValue) undoManager->perform (new SetPropertyAction (this, name, newValue, *existingValue, false, false)); } else { undoManager->perform (new SetPropertyAction (this, name, newValue, var::null, true, false)); } } } bool ValueTree::SharedObject::hasProperty (const Identifier& name) const { return properties.contains (name); } void ValueTree::SharedObject::removeProperty (const Identifier& name, UndoManager* const undoManager) { if (undoManager == nullptr) { if (properties.remove (name)) sendPropertyChangeMessage (name); } else { if (properties.contains (name)) undoManager->perform (new SetPropertyAction (this, name, var::null, properties [name], false, true)); } } void ValueTree::SharedObject::removeAllProperties (UndoManager* const undoManager) { if (undoManager == nullptr) { while (properties.size() > 0) { const Identifier name (properties.getName (properties.size() - 1)); properties.remove (name); sendPropertyChangeMessage (name); } } else { for (int i = properties.size(); --i >= 0;) undoManager->perform (new SetPropertyAction (this, properties.getName(i), var::null, properties.getValueAt(i), false, true)); } } ValueTree ValueTree::SharedObject::getChildWithName (const Identifier& typeToMatch) const { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->type == typeToMatch) return ValueTree (children.getUnchecked(i).getObject()); return ValueTree::invalid; } ValueTree ValueTree::SharedObject::getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager) { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->type == typeToMatch) return ValueTree (children.getUnchecked(i).getObject()); SharedObject* const newObject = new SharedObject (typeToMatch); addChild (newObject, -1, undoManager); return ValueTree (newObject); } ValueTree ValueTree::SharedObject::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { for (int i = 0; i < children.size(); ++i) if (children.getUnchecked(i)->getProperty (propertyName) == propertyValue) return ValueTree (children.getUnchecked(i).getObject()); return ValueTree::invalid; } bool ValueTree::SharedObject::isAChildOf (const SharedObject* const possibleParent) const { const SharedObject* p = parent; while (p != nullptr) { if (p == possibleParent) return true; p = p->parent; } return false; } int ValueTree::SharedObject::indexOf (const ValueTree& child) const { return children.indexOf (child.object); } void ValueTree::SharedObject::addChild (SharedObject* child, int index, UndoManager* const undoManager) { if (child != nullptr && child->parent != this) { if (child != this && ! isAChildOf (child)) { // You should always make sure that a child is removed from its previous parent before // adding it somewhere else - otherwise, it's ambiguous as to whether a different // undomanager should be used when removing it from its current parent.. jassert (child->parent == nullptr); if (child->parent != nullptr) { jassert (child->parent->children.indexOf (child) >= 0); child->parent->removeChild (child->parent->children.indexOf (child), undoManager); } if (undoManager == nullptr) { children.insert (index, child); child->parent = this; sendChildAddedMessage (ValueTree (child)); child->sendParentChangeMessage(); } else { if (index < 0) index = children.size(); undoManager->perform (new AddOrRemoveChildAction (this, index, child)); } } else { // You're attempting to create a recursive loop! A node // can't be a child of one of its own children! jassertfalse; } } } void ValueTree::SharedObject::removeChild (const int childIndex, UndoManager* const undoManager) { const SharedObjectPtr child (children [childIndex]); if (child != nullptr) { if (undoManager == nullptr) { children.remove (childIndex); child->parent = nullptr; sendChildRemovedMessage (ValueTree (child)); child->sendParentChangeMessage(); } else { undoManager->perform (new AddOrRemoveChildAction (this, childIndex, nullptr)); } } } void ValueTree::SharedObject::removeAllChildren (UndoManager* const undoManager) { while (children.size() > 0) removeChild (children.size() - 1, undoManager); } void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { // The source index must be a valid index! jassert (isPositiveAndBelow (currentIndex, children.size())); if (currentIndex != newIndex && isPositiveAndBelow (currentIndex, children.size())) { if (undoManager == nullptr) { children.move (currentIndex, newIndex); sendChildOrderChangedMessage(); } else { if (! isPositiveAndBelow (newIndex, children.size())) newIndex = children.size() - 1; undoManager->perform (new MoveChildAction (this, currentIndex, newIndex)); } } } void ValueTree::SharedObject::reorderChildren (const ReferenceCountedArray & newOrder, UndoManager* undoManager) { jassert (newOrder.size() == children.size()); if (undoManager == nullptr) { children = newOrder; sendChildOrderChangedMessage(); } else { for (int i = 0; i < children.size(); ++i) { const SharedObjectPtr child (newOrder.getUnchecked(i)); if (children.getUnchecked(i) != child) { const int oldIndex = children.indexOf (child); jassert (oldIndex >= 0); moveChild (oldIndex, i, undoManager); } } } } bool ValueTree::SharedObject::isEquivalentTo (const SharedObject& other) const { if (type != other.type || properties.size() != other.properties.size() || children.size() != other.children.size() || properties != other.properties) return false; for (int i = 0; i < children.size(); ++i) if (! children.getUnchecked(i)->isEquivalentTo (*other.children.getUnchecked(i))) return false; return true; } ValueTree::ValueTree() noexcept { } const ValueTree ValueTree::invalid; ValueTree::ValueTree (const Identifier& type_) : object (new ValueTree::SharedObject (type_)) { jassert (type_.toString().isNotEmpty()); // All objects should be given a sensible type name! } ValueTree::ValueTree (SharedObject* const object_) : object (object_) { } ValueTree::ValueTree (const ValueTree& other) : object (other.object) { } ValueTree& ValueTree::operator= (const ValueTree& other) { if (listeners.size() > 0) { if (object != nullptr) object->valueTreesWithListeners.removeValue (this); if (other.object != nullptr) other.object->valueTreesWithListeners.add (this); } object = other.object; return *this; } ValueTree::~ValueTree() { if (listeners.size() > 0 && object != nullptr) object->valueTreesWithListeners.removeValue (this); } bool ValueTree::operator== (const ValueTree& other) const noexcept { return object == other.object; } bool ValueTree::operator!= (const ValueTree& other) const noexcept { return object != other.object; } bool ValueTree::isEquivalentTo (const ValueTree& other) const { return object == other.object || (object != nullptr && other.object != nullptr && object->isEquivalentTo (*other.object)); } ValueTree ValueTree::createCopy() const { return ValueTree (object != nullptr ? new SharedObject (*object) : nullptr); } bool ValueTree::hasType (const Identifier& typeName) const { return object != nullptr && object->type == typeName; } Identifier ValueTree::getType() const { return object != nullptr ? object->type : Identifier(); } ValueTree ValueTree::getParent() const { return ValueTree (object != nullptr ? object->parent : (SharedObject*) nullptr); } ValueTree ValueTree::getSibling (const int delta) const { if (object == nullptr || object->parent == nullptr) return invalid; const int index = object->parent->indexOf (*this) + delta; return ValueTree (object->parent->children [index].getObject()); } const var& ValueTree::operator[] (const Identifier& name) const { return object == nullptr ? var::null : object->getProperty (name); } const var& ValueTree::getProperty (const Identifier& name) const { return object == nullptr ? var::null : object->getProperty (name); } var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const { return object == nullptr ? defaultReturnValue : object->getProperty (name, defaultReturnValue); } void ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) { jassert (name.toString().isNotEmpty()); if (object != nullptr && name.toString().isNotEmpty()) object->setProperty (name, newValue, undoManager); } bool ValueTree::hasProperty (const Identifier& name) const { return object != nullptr && object->hasProperty (name); } void ValueTree::removeProperty (const Identifier& name, UndoManager* const undoManager) { if (object != nullptr) object->removeProperty (name, undoManager); } void ValueTree::removeAllProperties (UndoManager* const undoManager) { if (object != nullptr) object->removeAllProperties (undoManager); } int ValueTree::getNumProperties() const { return object == nullptr ? 0 : object->properties.size(); } Identifier ValueTree::getPropertyName (const int index) const { return object == nullptr ? Identifier() : object->properties.getName (index); } class ValueTreePropertyValueSource : public Value::ValueSource, public ValueTree::Listener { public: ValueTreePropertyValueSource (const ValueTree& tree_, const Identifier& property_, UndoManager* const undoManager_) : tree (tree_), property (property_), undoManager (undoManager_) { tree.addListener (this); } ~ValueTreePropertyValueSource() { tree.removeListener (this); } const var getValue() const { return tree [property]; } void setValue (const var& newValue) { tree.setProperty (property, newValue, undoManager); } void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& changedProperty) { if (tree == treeWhosePropertyHasChanged && property == changedProperty) sendChangeMessage (false); } void valueTreeChildAdded (ValueTree&, ValueTree&) {} void valueTreeChildRemoved (ValueTree&, ValueTree&) {} void valueTreeChildOrderChanged (ValueTree&) {} void valueTreeParentChanged (ValueTree&) {} private: ValueTree tree; const Identifier property; UndoManager* const undoManager; ValueTreePropertyValueSource& operator= (const ValueTreePropertyValueSource&); }; Value ValueTree::getPropertyAsValue (const Identifier& name, UndoManager* const undoManager) const { return Value (new ValueTreePropertyValueSource (*this, name, undoManager)); } int ValueTree::getNumChildren() const { return object == nullptr ? 0 : object->children.size(); } ValueTree ValueTree::getChild (int index) const { return ValueTree (object != nullptr ? (SharedObject*) object->children [index] : (SharedObject*) nullptr); } ValueTree ValueTree::getChildWithName (const Identifier& type) const { return object != nullptr ? object->getChildWithName (type) : ValueTree::invalid; } ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) { return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; } ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; } bool ValueTree::isAChildOf (const ValueTree& possibleParent) const { return object != nullptr && object->isAChildOf (possibleParent.object); } int ValueTree::indexOf (const ValueTree& child) const { return object != nullptr ? object->indexOf (child) : -1; } void ValueTree::addChild (const ValueTree& child, int index, UndoManager* const undoManager) { if (object != nullptr) object->addChild (child.object, index, undoManager); } void ValueTree::removeChild (const int childIndex, UndoManager* const undoManager) { if (object != nullptr) object->removeChild (childIndex, undoManager); } void ValueTree::removeChild (const ValueTree& child, UndoManager* const undoManager) { if (object != nullptr) object->removeChild (object->children.indexOf (child.object), undoManager); } void ValueTree::removeAllChildren (UndoManager* const undoManager) { if (object != nullptr) object->removeAllChildren (undoManager); } void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { if (object != nullptr) object->moveChild (currentIndex, newIndex, undoManager); } void ValueTree::addListener (Listener* listener) { if (listener != nullptr) { if (listeners.size() == 0 && object != nullptr) object->valueTreesWithListeners.add (this); listeners.add (listener); } } void ValueTree::removeListener (Listener* listener) { listeners.remove (listener); if (listeners.size() == 0 && object != nullptr) object->valueTreesWithListeners.removeValue (this); } XmlElement* ValueTree::SharedObject::createXml() const { XmlElement* const xml = new XmlElement (type.toString()); properties.copyToXmlAttributes (*xml); for (int i = 0; i < children.size(); ++i) xml->addChildElement (children.getUnchecked(i)->createXml()); return xml; } XmlElement* ValueTree::createXml() const { return object != nullptr ? object->createXml() : nullptr; } ValueTree ValueTree::fromXml (const XmlElement& xml) { ValueTree v (xml.getTagName()); v.object->properties.setFromXmlAttributes (xml); forEachXmlChildElement (xml, e) v.addChild (fromXml (*e), -1, nullptr); return v; } void ValueTree::writeToStream (OutputStream& output) { output.writeString (getType().toString()); const int numProps = getNumProperties(); output.writeCompressedInt (numProps); int i; for (i = 0; i < numProps; ++i) { const Identifier name (getPropertyName(i)); output.writeString (name.toString()); getProperty(name).writeToStream (output); } const int numChildren = getNumChildren(); output.writeCompressedInt (numChildren); for (i = 0; i < numChildren; ++i) getChild (i).writeToStream (output); } ValueTree ValueTree::readFromStream (InputStream& input) { const String type (input.readString()); if (type.isEmpty()) return ValueTree::invalid; ValueTree v (type); const int numProps = input.readCompressedInt(); if (numProps < 0) { jassertfalse; // trying to read corrupted data! return v; } int i; for (i = 0; i < numProps; ++i) { const String name (input.readString()); jassert (name.isNotEmpty()); const var value (var::readFromStream (input)); v.object->properties.set (name, value); } const int numChildren = input.readCompressedInt(); for (i = 0; i < numChildren; ++i) { ValueTree child (readFromStream (input)); v.object->children.add (child.object); child.object->parent = v.object; } return v; } ValueTree ValueTree::readFromData (const void* const data, const size_t numBytes) { MemoryInputStream in (data, numBytes, false); return readFromStream (in); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ValueTree.cpp ***/ /*** Start of inlined file: juce_Value.cpp ***/ BEGIN_JUCE_NAMESPACE Value::ValueSource::ValueSource() { } Value::ValueSource::~ValueSource() { } void Value::ValueSource::sendChangeMessage (const bool synchronous) { if (synchronous) { // (hold a local reference to this object in case it's freed during the callbacks) const ReferenceCountedObjectPtr localRef (this); for (int i = valuesWithListeners.size(); --i >= 0;) { Value* const v = valuesWithListeners[i]; if (v != nullptr) v->callListeners(); } } else { triggerAsyncUpdate(); } } void Value::ValueSource::handleAsyncUpdate() { sendChangeMessage (true); } class SimpleValueSource : public Value::ValueSource { public: SimpleValueSource() { } SimpleValueSource (const var& initialValue) : value (initialValue) { } const var getValue() const { return value; } void setValue (const var& newValue) { if (! newValue.equalsWithSameType (value)) { value = newValue; sendChangeMessage (false); } } private: var value; JUCE_DECLARE_NON_COPYABLE (SimpleValueSource); }; Value::Value() : value (new SimpleValueSource()) { } Value::Value (ValueSource* const value_) : value (value_) { jassert (value_ != nullptr); } Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue)) { } Value::Value (const Value& other) : value (other.value) { } Value& Value::operator= (const Value& other) { value = other.value; return *this; } Value::~Value() { if (listeners.size() > 0) value->valuesWithListeners.removeValue (this); } var Value::getValue() const { return value->getValue(); } Value::operator var() const { return getValue(); } void Value::setValue (const var& newValue) { value->setValue (newValue); } String Value::toString() const { return value->getValue().toString(); } Value& Value::operator= (const var& newValue) { value->setValue (newValue); return *this; } void Value::referTo (const Value& valueToReferTo) { if (valueToReferTo.value != value) { if (listeners.size() > 0) { value->valuesWithListeners.removeValue (this); valueToReferTo.value->valuesWithListeners.add (this); } value = valueToReferTo.value; callListeners(); } } bool Value::refersToSameSourceAs (const Value& other) const { return value == other.value; } bool Value::operator== (const Value& other) const { return value == other.value || value->getValue() == other.getValue(); } bool Value::operator!= (const Value& other) const { return value != other.value && value->getValue() != other.getValue(); } void Value::addListener (ValueListener* const listener) { if (listener != nullptr) { if (listeners.size() == 0) value->valuesWithListeners.add (this); listeners.add (listener); } } void Value::removeListener (ValueListener* const listener) { listeners.remove (listener); if (listeners.size() == 0) value->valuesWithListeners.removeValue (this); } void Value::callListeners() { Value v (*this); // (create a copy in case this gets deleted by a callback) listeners.call (&ValueListener::valueChanged, v); } OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value) { return stream << value.toString(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Value.cpp ***/ /*** Start of inlined file: juce_Application.cpp ***/ BEGIN_JUCE_NAMESPACE #if JUCE_MAC extern void juce_initialiseMacMainMenu(); #endif class AppBroadcastCallback : public ActionListener { public: AppBroadcastCallback() { MessageManager::getInstance()->registerBroadcastListener (this); } ~AppBroadcastCallback() { MessageManager::getInstance()->deregisterBroadcastListener (this); } void actionListenerCallback (const String& message) { JUCEApplication* const app = JUCEApplication::getInstance(); if (app != 0 && message.startsWith (app->getApplicationName() + "/")) app->anotherInstanceStarted (message.substring (app->getApplicationName().length() + 1)); } }; JUCEApplication::JUCEApplication() : appReturnValue (0), stillInitialising (true) { jassert (isStandaloneApp() && appInstance == nullptr); appInstance = this; } JUCEApplication::~JUCEApplication() { if (appLock != nullptr) { appLock->exit(); appLock = nullptr; } jassert (appInstance == this); appInstance = nullptr; } JUCEApplication::CreateInstanceFunction JUCEApplication::createInstance = 0; JUCEApplication* JUCEApplication::appInstance = nullptr; bool JUCEApplication::moreThanOneInstanceAllowed() { return true; } void JUCEApplication::anotherInstanceStarted (const String&) { } void JUCEApplication::systemRequestedQuit() { quit(); } void JUCEApplication::quit() { MessageManager::getInstance()->stopDispatchLoop(); } void JUCEApplication::setApplicationReturnValue (const int newReturnValue) noexcept { appReturnValue = newReturnValue; } void JUCEApplication::unhandledException (const std::exception*, const String&, const int) { jassertfalse; } void JUCEApplication::sendUnhandledException (const std::exception* const e, const char* const sourceFile, const int lineNumber) { if (appInstance != nullptr) appInstance->unhandledException (e, sourceFile, lineNumber); } ApplicationCommandTarget* JUCEApplication::getNextCommandTarget() { return nullptr; } void JUCEApplication::getAllCommands (Array & commands) { commands.add (StandardApplicationCommandIDs::quit); } void JUCEApplication::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) { if (commandID == StandardApplicationCommandIDs::quit) { result.setInfo (TRANS("Quit"), TRANS("Quits the application"), "Application", 0); result.defaultKeypresses.add (KeyPress ('q', ModifierKeys::commandModifier, 0)); } } bool JUCEApplication::perform (const InvocationInfo& info) { if (info.commandID == StandardApplicationCommandIDs::quit) { systemRequestedQuit(); return true; } return false; } bool JUCEApplication::initialiseApp (const String& commandLine) { commandLineParameters = commandLine.trim(); #if ! JUCE_IOS jassert (appLock == nullptr); // initialiseApp must only be called once! if (! moreThanOneInstanceAllowed()) { appLock = new InterProcessLock ("juceAppLock_" + getApplicationName()); if (! appLock->enter(0)) { appLock = nullptr; MessageManager::broadcastMessage (getApplicationName() + "/" + commandLineParameters); DBG ("Another instance is running - quitting..."); return false; } } #endif // let the app do its setting-up.. initialise (commandLineParameters); #if JUCE_MAC juce_initialiseMacMainMenu(); // needs to be called after the app object has created, to get its name #endif #if ! JUCE_IOS broadcastCallback = new AppBroadcastCallback(); #endif stillInitialising = false; return true; } int JUCEApplication::shutdownApp() { jassert (appInstance == this); broadcastCallback = nullptr; JUCE_TRY { // give the app a chance to clean up.. shutdown(); } JUCE_CATCH_EXCEPTION return getApplicationReturnValue(); } // This is called on the Mac and iOS where the OS doesn't allow the stack to unwind on shutdown.. void JUCEApplication::appWillTerminateByForce() { { const ScopedPointer app (JUCEApplication::getInstance()); if (app != nullptr) app->shutdownApp(); } shutdownJuce_GUI(); } #if ! JUCE_ANDROID int JUCEApplication::main (const String& commandLine) { ScopedJuceInitialiser_GUI libraryInitialiser; jassert (createInstance != nullptr); int returnCode = 0; { const ScopedPointer app (createInstance()); if (! app->initialiseApp (commandLine)) return 0; JUCE_TRY { // loop until a quit message is received.. MessageManager::getInstance()->runDispatchLoop(); } JUCE_CATCH_EXCEPTION returnCode = app->shutdownApp(); } return returnCode; } #if JUCE_IOS extern int juce_iOSMain (int argc, const char* argv[]); #endif #if ! JUCE_WINDOWS extern const char* juce_Argv0; #endif int JUCEApplication::main (int argc, const char* argv[]) { JUCE_AUTORELEASEPOOL #if ! JUCE_WINDOWS jassert (createInstance != nullptr); juce_Argv0 = argv[0]; #endif #if JUCE_IOS return juce_iOSMain (argc, argv); #else String cmd; for (int i = 1; i < argc; ++i) cmd << argv[i] << ' '; return JUCEApplication::main (cmd); #endif } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_Application.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandInfo.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandInfo::ApplicationCommandInfo (const CommandID commandID_) noexcept : commandID (commandID_), flags (0) { } void ApplicationCommandInfo::setInfo (const String& shortName_, const String& description_, const String& categoryName_, const int flags_) noexcept { shortName = shortName_; description = description_; categoryName = categoryName_; flags = flags_; } void ApplicationCommandInfo::setActive (const bool b) noexcept { if (b) flags &= ~isDisabled; else flags |= isDisabled; } void ApplicationCommandInfo::setTicked (const bool b) noexcept { if (b) flags |= isTicked; else flags &= ~isTicked; } void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, const ModifierKeys& modifiers) noexcept { defaultKeypresses.add (KeyPress (keyCode, modifiers, 0)); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandInfo.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandManager.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandManager::ApplicationCommandManager() : firstTarget (nullptr) { keyMappings = new KeyPressMappingSet (this); Desktop::getInstance().addFocusChangeListener (this); } ApplicationCommandManager::~ApplicationCommandManager() { Desktop::getInstance().removeFocusChangeListener (this); keyMappings = nullptr; } void ApplicationCommandManager::clearCommands() { commands.clear(); keyMappings->clearAllKeyPresses(); triggerAsyncUpdate(); } void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand) { // zero isn't a valid command ID! jassert (newCommand.commandID != 0); // the name isn't optional! jassert (newCommand.shortName.isNotEmpty()); if (getCommandForID (newCommand.commandID) == 0) { ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand); newInfo->flags &= ~ApplicationCommandInfo::isTicked; commands.add (newInfo); keyMappings->resetToDefaultMapping (newCommand.commandID); triggerAsyncUpdate(); } else { // trying to re-register the same command with different parameters? jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName && (newCommand.description == getCommandForID (newCommand.commandID)->description || newCommand.description.isEmpty()) && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)) == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))); } } void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target) { if (target != nullptr) { Array commandIDs; target->getAllCommands (commandIDs); for (int i = 0; i < commandIDs.size(); ++i) { ApplicationCommandInfo info (commandIDs.getUnchecked(i)); target->getCommandInfo (info.commandID, info); registerCommand (info); } } } void ApplicationCommandManager::removeCommand (const CommandID commandID) { for (int i = commands.size(); --i >= 0;) { if (commands.getUnchecked (i)->commandID == commandID) { commands.remove (i); triggerAsyncUpdate(); const Array keys (keyMappings->getKeyPressesAssignedToCommand (commandID)); for (int j = keys.size(); --j >= 0;) keyMappings->removeKeyPress (keys.getReference (j)); } } } void ApplicationCommandManager::commandStatusChanged() { triggerAsyncUpdate(); } const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const noexcept { for (int i = commands.size(); --i >= 0;) if (commands.getUnchecked(i)->commandID == commandID) return commands.getUnchecked(i); return nullptr; } const String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const noexcept { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return ci != nullptr ? ci->shortName : String::empty; } const String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const noexcept { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return ci != nullptr ? (ci->description.isNotEmpty() ? ci->description : ci->shortName) : String::empty; } StringArray ApplicationCommandManager::getCommandCategories() const { StringArray s; for (int i = 0; i < commands.size(); ++i) s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false); return s; } const Array ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const { Array results; for (int i = 0; i < commands.size(); ++i) if (commands.getUnchecked(i)->categoryName == categoryName) results.add (commands.getUnchecked(i)->commandID); return results; } bool ApplicationCommandManager::invokeDirectly (const CommandID commandID, const bool asynchronously) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; return invoke (info, asynchronously); } bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& info_, const bool asynchronously) { // This call isn't thread-safe for use from a non-UI thread without locking the message // manager first.. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); ApplicationCommandInfo commandInfo (0); ApplicationCommandTarget* const target = getTargetForCommand (info_.commandID, commandInfo); if (target == nullptr) return false; ApplicationCommandTarget::InvocationInfo info (info_); info.commandFlags = commandInfo.flags; sendListenerInvokeCallback (info); const bool ok = target->invoke (info, asynchronously); commandStatusChanged(); return ok; } ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (const CommandID) { return firstTarget != nullptr ? firstTarget : findDefaultComponentTarget(); } void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) noexcept { firstTarget = newTarget; } ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID, ApplicationCommandInfo& upToDateInfo) { ApplicationCommandTarget* target = getFirstCommandTarget (commandID); if (target == nullptr) target = JUCEApplication::getInstance(); if (target != nullptr) target = target->getTargetForCommand (commandID); if (target != nullptr) target->getCommandInfo (commandID, upToDateInfo); return target; } ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c) { ApplicationCommandTarget* target = dynamic_cast (c); if (target == nullptr && c != nullptr) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) target = c->findParentComponentOfClass ((ApplicationCommandTarget*) nullptr); return target; } ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget() { Component* c = Component::getCurrentlyFocusedComponent(); if (c == nullptr) { TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow(); if (activeWindow != nullptr) { c = activeWindow->getPeer()->getLastFocusedSubcomponent(); if (c == nullptr) c = activeWindow; } } if (c == nullptr && Process::isForegroundProcess()) { // getting a bit desperate now - try all desktop comps.. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;) { ApplicationCommandTarget* const target = findTargetForComponent (Desktop::getInstance().getComponent (i) ->getPeer()->getLastFocusedSubcomponent()); if (target != nullptr) return target; } } if (c != nullptr) { ResizableWindow* const resizableWindow = dynamic_cast (c); // if we're focused on a ResizableWindow, chances are that it's the content // component that really should get the event. And if not, the event will // still be passed up to the top level window anyway, so let's send it to the // content comp. if (resizableWindow != nullptr && resizableWindow->getContentComponent() != nullptr) c = resizableWindow->getContentComponent(); ApplicationCommandTarget* const target = findTargetForComponent (c); if (target != nullptr) return target; } return JUCEApplication::getInstance(); } void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* const listener) { listeners.add (listener); } void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* const listener) { listeners.remove (listener); } void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info) { listeners.call (&ApplicationCommandManagerListener::applicationCommandInvoked, info); } void ApplicationCommandManager::handleAsyncUpdate() { listeners.call (&ApplicationCommandManagerListener::applicationCommandListChanged); } void ApplicationCommandManager::globalFocusChanged (Component*) { commandStatusChanged(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandManager.cpp ***/ /*** Start of inlined file: juce_ApplicationCommandTarget.cpp ***/ BEGIN_JUCE_NAMESPACE ApplicationCommandTarget::ApplicationCommandTarget() { } ApplicationCommandTarget::~ApplicationCommandTarget() { messageInvoker = nullptr; } bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async) { if (isCommandActive (info.commandID)) { if (async) { if (messageInvoker == nullptr) messageInvoker = new CommandTargetMessageInvoker (this); messageInvoker->postMessage (new Message (0, 0, 0, new ApplicationCommandTarget::InvocationInfo (info))); return true; } else { const bool success = perform (info); jassert (success); // hmm - your target should have been able to perform this command. If it can't // do it at the moment for some reason, it should clear the 'isActive' flag when it // returns the command's info. return success; } } return false; } ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent() { Component* c = dynamic_cast (this); if (c != nullptr) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) return c->findParentComponentOfClass ((ApplicationCommandTarget*) nullptr); return nullptr; } ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID) { ApplicationCommandTarget* target = this; int depth = 0; while (target != nullptr) { Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) return target; target = target->getNextCommandTarget(); ++depth; jassert (depth < 100); // could be a recursive command chain?? jassert (target != this); // definitely a recursive command chain! if (depth > 100 || target == this) break; } if (target == nullptr) { target = JUCEApplication::getInstance(); if (target != nullptr) { Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) return target; } } return nullptr; } bool ApplicationCommandTarget::isCommandActive (const CommandID commandID) { ApplicationCommandInfo info (commandID); info.flags = ApplicationCommandInfo::isDisabled; getCommandInfo (commandID, info); return (info.flags & ApplicationCommandInfo::isDisabled) == 0; } bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async) { ApplicationCommandTarget* target = this; int depth = 0; while (target != nullptr) { if (target->tryToInvoke (info, async)) return true; target = target->getNextCommandTarget(); ++depth; jassert (depth < 100); // could be a recursive command chain?? jassert (target != this); // definitely a recursive command chain! if (depth > 100 || target == this) break; } if (target == nullptr) { target = JUCEApplication::getInstance(); if (target != nullptr) return target->tryToInvoke (info, async); } return false; } bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; return invoke (info, asynchronously); } ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID commandID_) : commandID (commandID_), commandFlags (0), invocationMethod (direct), originatingComponent (nullptr), isKeyDown (false), millisecsSinceKeyPressed (0) { } ApplicationCommandTarget::CommandTargetMessageInvoker::CommandTargetMessageInvoker (ApplicationCommandTarget* const owner_) : owner (owner_) { } ApplicationCommandTarget::CommandTargetMessageInvoker::~CommandTargetMessageInvoker() { } void ApplicationCommandTarget::CommandTargetMessageInvoker::handleMessage (const Message& message) { const ScopedPointer info (static_cast (message.pointerParameter)); owner->tryToInvoke (*info, false); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationCommandTarget.cpp ***/ /*** Start of inlined file: juce_ApplicationProperties.cpp ***/ BEGIN_JUCE_NAMESPACE juce_ImplementSingleton (ApplicationProperties) ApplicationProperties::ApplicationProperties() : msBeforeSaving (3000), options (PropertiesFile::storeAsBinary), commonSettingsAreReadOnly (0), processLock (nullptr) { } ApplicationProperties::~ApplicationProperties() { closeFiles(); clearSingletonInstance(); } void ApplicationProperties::setStorageParameters (const String& applicationName, const String& fileNameSuffix, const String& folderName_, const int millisecondsBeforeSaving, const int propertiesFileOptions, InterProcessLock* processLock_) { appName = applicationName; fileSuffix = fileNameSuffix; folderName = folderName_; msBeforeSaving = millisecondsBeforeSaving; options = propertiesFileOptions; processLock = processLock_; } bool ApplicationProperties::testWriteAccess (const bool testUserSettings, const bool testCommonSettings, const bool showWarningDialogOnFailure) { const bool userOk = (! testUserSettings) || getUserSettings()->save(); const bool commonOk = (! testCommonSettings) || getCommonSettings (false)->save(); if (! (userOk && commonOk)) { if (showWarningDialogOnFailure) { String filenames; if (userProps != nullptr && ! userOk) filenames << '\n' << userProps->getFile().getFullPathName(); if (commonProps != nullptr && ! commonOk) filenames << '\n' << commonProps->getFile().getFullPathName(); AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, appName + TRANS(" - Unable to save settings"), TRANS("An error occurred when trying to save the application's settings file...\n\nIn order to save and restore its settings, ") + appName + TRANS(" needs to be able to write to the following files:\n") + filenames + TRANS("\n\nMake sure that these files aren't read-only, and that the disk isn't full.")); } return false; } return true; } void ApplicationProperties::openFiles() { // You need to call setStorageParameters() before trying to get hold of the // properties! jassert (appName.isNotEmpty()); if (appName.isNotEmpty()) { if (userProps == nullptr) userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, false, msBeforeSaving, options, processLock); if (commonProps == nullptr) commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, true, msBeforeSaving, options, processLock); userProps->setFallbackPropertySet (commonProps); } } PropertiesFile* ApplicationProperties::getUserSettings() { if (userProps == nullptr) openFiles(); return userProps; } PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) { if (commonProps == nullptr) openFiles(); if (returnUserPropsIfReadOnly) { if (commonSettingsAreReadOnly == 0) commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; if (commonSettingsAreReadOnly > 0) return userProps; } return commonProps; } bool ApplicationProperties::saveIfNeeded() { return (userProps == nullptr || userProps->saveIfNeeded()) && (commonProps == nullptr || commonProps->saveIfNeeded()); } void ApplicationProperties::closeFiles() { userProps = nullptr; commonProps = nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_ApplicationProperties.cpp ***/ /*** Start of inlined file: juce_PropertiesFile.cpp ***/ BEGIN_JUCE_NAMESPACE namespace PropertyFileConstants { static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP"); static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP"); static const char* const fileTag = "PROPERTIES"; static const char* const valueTag = "VALUE"; static const char* const nameAttribute = "name"; static const char* const valueAttribute = "val"; } PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSaving, const int options_, InterProcessLock* const processLock_) : PropertySet (ignoreCaseOfKeyNames), file (f), timerInterval (millisecondsBeforeSaving), options (options_), loadedOk (false), needsWriting (false), processLock (processLock_) { // You need to correctly specify just one storage format for the file jassert ((options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsCompressedBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsXML); ProcessScopedLock pl (createProcessLock()); if (pl != nullptr && ! pl->isLocked()) return; // locking failure.. ScopedPointer fileStream (f.createInputStream()); if (fileStream != nullptr) { int magicNumber = fileStream->readInt(); if (magicNumber == PropertyFileConstants::magicNumberCompressed) { fileStream = new GZIPDecompressorInputStream (new SubregionStream (fileStream.release(), 4, -1, true), true); magicNumber = PropertyFileConstants::magicNumber; } if (magicNumber == PropertyFileConstants::magicNumber) { loadedOk = true; BufferedInputStream in (fileStream.release(), 2048, true); int numValues = in.readInt(); while (--numValues >= 0 && ! in.isExhausted()) { const String key (in.readString()); const String value (in.readString()); jassert (key.isNotEmpty()); if (key.isNotEmpty()) getAllProperties().set (key, value); } } else { // Not a binary props file - let's see if it's XML.. fileStream = nullptr; XmlDocument parser (f); ScopedPointer doc (parser.getDocumentElement (true)); if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag)) { doc = parser.getDocumentElement(); if (doc != nullptr) { loadedOk = true; forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) { const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); if (name.isNotEmpty()) { getAllProperties().set (name, e->getFirstChildElement() != nullptr ? e->getFirstChildElement()->createDocument (String::empty, true) : e->getStringAttribute (PropertyFileConstants::valueAttribute)); } } } else { // must be a pretty broken XML file we're trying to parse here, // or a sign that this object needs an InterProcessLock, // or just a failure reading the file. This last reason is why // we don't jassertfalse here. } } } } else { loadedOk = ! f.exists(); } } PropertiesFile::~PropertiesFile() { if (! saveIfNeeded()) jassertfalse; } InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const { return processLock != nullptr ? new InterProcessLock::ScopedLockType (*processLock) : nullptr; } bool PropertiesFile::saveIfNeeded() { const ScopedLock sl (getLock()); return (! needsWriting) || save(); } bool PropertiesFile::needsToBeSaved() const { const ScopedLock sl (getLock()); return needsWriting; } void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) { const ScopedLock sl (getLock()); needsWriting = needsToBeSaved_; } bool PropertiesFile::save() { const ScopedLock sl (getLock()); stopTimer(); if (file == File::nonexistent || file.isDirectory() || ! file.getParentDirectory().createDirectory()) return false; if ((options & storeAsXML) != 0) { XmlElement doc (PropertyFileConstants::fileTag); for (int i = 0; i < getAllProperties().size(); ++i) { XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]); // if the value seems to contain xml, store it as such.. XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i]); if (childElement != nullptr) e->addChildElement (childElement); else e->setAttribute (PropertyFileConstants::valueAttribute, getAllProperties().getAllValues() [i]); } ProcessScopedLock pl (createProcessLock()); if (pl != nullptr && ! pl->isLocked()) return false; // locking failure.. if (doc.writeToFile (file, String::empty)) { needsWriting = false; return true; } } else { ProcessScopedLock pl (createProcessLock()); if (pl != nullptr && ! pl->isLocked()) return false; // locking failure.. TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != nullptr) { if ((options & storeAsCompressedBinary) != 0) { out->writeInt (PropertyFileConstants::magicNumberCompressed); out->flush(); out = new GZIPCompressorOutputStream (out.release(), 9, true); } else { // have you set up the storage option flags correctly? jassert ((options & storeAsBinary) != 0); out->writeInt (PropertyFileConstants::magicNumber); } const int numProperties = getAllProperties().size(); out->writeInt (numProperties); for (int i = 0; i < numProperties; ++i) { out->writeString (getAllProperties().getAllKeys() [i]); out->writeString (getAllProperties().getAllValues() [i]); } out = nullptr; if (tempFile.overwriteTargetFileWithTemporary()) { needsWriting = false; return true; } } } return false; } void PropertiesFile::timerCallback() { saveIfNeeded(); } void PropertiesFile::propertyChanged() { sendChangeMessage(); needsWriting = true; if (timerInterval > 0) startTimer (timerInterval); else if (timerInterval == 0) saveIfNeeded(); } File PropertiesFile::getDefaultAppSettingsFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers) { // mustn't have illegal characters in this name.. jassert (applicationName == File::createLegalFileName (applicationName)); #if JUCE_MAC || JUCE_IOS File dir (commonToAllUsers ? "/Library/Preferences" : "~/Library/Preferences"); if (folderName.isNotEmpty()) dir = dir.getChildFile (folderName); #elif JUCE_LINUX || JUCE_ANDROID const File dir ((commonToAllUsers ? "/var/" : "~/") + (folderName.isNotEmpty() ? folderName : ("." + applicationName))); #elif JUCE_WINDOWS File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory : File::userApplicationDataDirectory)); if (dir == File::nonexistent) return File::nonexistent; dir = dir.getChildFile (folderName.isNotEmpty() ? folderName : applicationName); #endif return dir.getChildFile (applicationName) .withFileExtension (fileNameSuffix); } PropertiesFile* PropertiesFile::createDefaultAppPropertiesFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers, const int millisecondsBeforeSaving, const int propertiesFileOptions, InterProcessLock* processLock_) { const File file (getDefaultAppSettingsFile (applicationName, fileNameSuffix, folderName, commonToAllUsers)); jassert (file != File::nonexistent); if (file == File::nonexistent) return nullptr; return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_); } END_JUCE_NAMESPACE /*** End of inlined file: juce_PropertiesFile.cpp ***/ /*** Start of inlined file: juce_FileBasedDocument.cpp ***/ BEGIN_JUCE_NAMESPACE FileBasedDocument::FileBasedDocument (const String& fileExtension_, const String& fileWildcard_, const String& openFileDialogTitle_, const String& saveFileDialogTitle_) : changedSinceSave (false), fileExtension (fileExtension_), fileWildcard (fileWildcard_), openFileDialogTitle (openFileDialogTitle_), saveFileDialogTitle (saveFileDialogTitle_) { } FileBasedDocument::~FileBasedDocument() { } void FileBasedDocument::setChangedFlag (const bool hasChanged) { if (changedSinceSave != hasChanged) { changedSinceSave = hasChanged; sendChangeMessage(); } } void FileBasedDocument::changed() { changedSinceSave = true; sendChangeMessage(); } void FileBasedDocument::setFile (const File& newFile) { if (documentFile != newFile) { documentFile = newFile; changed(); } } #if JUCE_MODAL_LOOPS_PERMITTED bool FileBasedDocument::loadFrom (const File& newFile, const bool showMessageOnFailure) { MouseCursor::showWaitCursor(); const File oldFile (documentFile); documentFile = newFile; String error; if (newFile.existsAsFile()) { error = loadDocument (newFile); if (error.isEmpty()) { setChangedFlag (false); MouseCursor::hideWaitCursor(); setLastDocumentOpened (newFile); return true; } } else { error = "The file doesn't exist"; } documentFile = oldFile; MouseCursor::hideWaitCursor(); if (showMessageOnFailure) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Failed to open file..."), TRANS("There was an error while trying to load the file:\n\n") + newFile.getFullPathName() + "\n\n" + error); } return false; } bool FileBasedDocument::loadFromUserSpecifiedFile (const bool showMessageOnFailure) { FileChooser fc (openFileDialogTitle, getLastDocumentOpened(), fileWildcard); if (fc.browseForFileToOpen()) return loadFrom (fc.getResult(), showMessageOnFailure); return false; } FileBasedDocument::SaveResult FileBasedDocument::save (const bool askUserForFileIfNotSpecified, const bool showMessageOnFailure) { return saveAs (documentFile, false, askUserForFileIfNotSpecified, showMessageOnFailure); } FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile, const bool warnAboutOverwritingExistingFiles, const bool askUserForFileIfNotSpecified, const bool showMessageOnFailure) { if (newFile == File::nonexistent) { if (askUserForFileIfNotSpecified) { return saveAsInteractive (true); } else { // can't save to an unspecified file jassertfalse; return failedToWriteToFile; } } if (warnAboutOverwritingExistingFiles && newFile.exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:\n\n") + newFile.getFullPathName() + TRANS("\n\nAre you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) { return userCancelledSave; } } MouseCursor::showWaitCursor(); const File oldFile (documentFile); documentFile = newFile; String error (saveDocument (newFile)); if (error.isEmpty()) { setChangedFlag (false); MouseCursor::hideWaitCursor(); return savedOk; } documentFile = oldFile; MouseCursor::hideWaitCursor(); if (showMessageOnFailure) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Error writing to file..."), TRANS("An error occurred while trying to save \"") + getDocumentTitle() + TRANS("\" to the file:\n\n") + newFile.getFullPathName() + "\n\n" + error); } return failedToWriteToFile; } FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees() { if (! hasChangedSinceSaved()) return savedOk; const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, TRANS("Closing document..."), TRANS("Do you want to save the changes to \"") + getDocumentTitle() + "\"?", TRANS("save"), TRANS("discard changes"), TRANS("cancel")); if (r == 1) { // save changes return save (true, true); } else if (r == 2) { // discard changes return savedOk; } return userCancelledSave; } FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (const bool warnAboutOverwritingExistingFiles) { File f; if (documentFile.existsAsFile()) f = documentFile; else f = getLastDocumentOpened(); String legalFilename (File::createLegalFileName (getDocumentTitle())); if (legalFilename.isEmpty()) legalFilename = "unnamed"; if (f.existsAsFile() || f.getParentDirectory().isDirectory()) f = f.getSiblingFile (legalFilename); else f = File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename); f = f.withFileExtension (fileExtension) .getNonexistentSibling (true); FileChooser fc (saveFileDialogTitle, f, fileWildcard); if (fc.browseForFileToSave (warnAboutOverwritingExistingFiles)) { File chosen (fc.getResult()); if (chosen.getFileExtension().isEmpty()) { chosen = chosen.withFileExtension (fileExtension); if (chosen.exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:") + "\n\n" + chosen.getFullPathName() + "\n\n" + TRANS("Are you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) { return userCancelledSave; } } } setLastDocumentOpened (chosen); return saveAs (chosen, false, false, true); } return userCancelledSave; } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_FileBasedDocument.cpp ***/ /*** Start of inlined file: juce_RecentlyOpenedFilesList.cpp ***/ BEGIN_JUCE_NAMESPACE RecentlyOpenedFilesList::RecentlyOpenedFilesList() : maxNumberOfItems (10) { } RecentlyOpenedFilesList::~RecentlyOpenedFilesList() { } void RecentlyOpenedFilesList::setMaxNumberOfItems (const int newMaxNumber) { maxNumberOfItems = jmax (1, newMaxNumber); files.removeRange (maxNumberOfItems, getNumFiles()); } int RecentlyOpenedFilesList::getNumFiles() const { return files.size(); } File RecentlyOpenedFilesList::getFile (const int index) const { return File (files [index]); } void RecentlyOpenedFilesList::clear() { files.clear(); } void RecentlyOpenedFilesList::addFile (const File& file) { removeFile (file); files.insert (0, file.getFullPathName()); setMaxNumberOfItems (maxNumberOfItems); } void RecentlyOpenedFilesList::removeFile (const File& file) { files.removeString (file.getFullPathName()); } void RecentlyOpenedFilesList::removeNonExistentFiles() { for (int i = getNumFiles(); --i >= 0;) if (! getFile(i).exists()) files.remove (i); } int RecentlyOpenedFilesList::createPopupMenuItems (PopupMenu& menuToAddTo, const int baseItemId, const bool showFullPaths, const bool dontAddNonExistentFiles, const File** filesToAvoid) { int num = 0; for (int i = 0; i < getNumFiles(); ++i) { const File f (getFile(i)); if ((! dontAddNonExistentFiles) || f.exists()) { bool needsAvoiding = false; if (filesToAvoid != nullptr) { for (const File** avoid = filesToAvoid; *avoid != nullptr; ++avoid) { if (f == **avoid) { needsAvoiding = true; break; } } } if (! needsAvoiding) { menuToAddTo.addItem (baseItemId + i, showFullPaths ? f.getFullPathName() : f.getFileName()); ++num; } } } return num; } String RecentlyOpenedFilesList::toString() const { return files.joinIntoString ("\n"); } void RecentlyOpenedFilesList::restoreFromString (const String& stringifiedVersion) { clear(); files.addLines (stringifiedVersion); setMaxNumberOfItems (maxNumberOfItems); } END_JUCE_NAMESPACE /*** End of inlined file: juce_RecentlyOpenedFilesList.cpp ***/ /*** Start of inlined file: juce_UndoManager.cpp ***/ BEGIN_JUCE_NAMESPACE UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, const int minimumTransactions) : totalUnitsStored (0), nextIndex (0), newTransaction (true), reentrancyCheck (false) { setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, minimumTransactions); } UndoManager::~UndoManager() { clearUndoHistory(); } void UndoManager::clearUndoHistory() { transactions.clear(); transactionNames.clear(); totalUnitsStored = 0; nextIndex = 0; sendChangeMessage(); } int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const { return totalUnitsStored; } void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, const int minimumTransactions) { maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); minimumTransactionsToKeep = jmax (1, minimumTransactions); } bool UndoManager::perform (UndoableAction* const command_, const String& actionName) { if (command_ != nullptr) { ScopedPointer command (command_); if (actionName.isNotEmpty()) currentTransactionName = actionName; if (reentrancyCheck) { jassertfalse; // don't call perform() recursively from the UndoableAction::perform() or // undo() methods, or else these actions won't actually get done. return false; } else if (command->perform()) { OwnedArray* commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) { UndoableAction* lastAction = commandSet->getLast(); if (lastAction != nullptr) { UndoableAction* coalescedAction = lastAction->createCoalescedAction (command); if (coalescedAction != nullptr) { command = coalescedAction; totalUnitsStored -= lastAction->getSizeInUnits(); commandSet->removeLast(); } } } else { commandSet = new OwnedArray(); transactions.insert (nextIndex, commandSet); transactionNames.insert (nextIndex, currentTransactionName); ++nextIndex; } totalUnitsStored += command->getSizeInUnits(); commandSet->add (command.release()); newTransaction = false; while (nextIndex < transactions.size()) { const OwnedArray * const lastSet = transactions.getLast(); for (int i = lastSet->size(); --i >= 0;) totalUnitsStored -= lastSet->getUnchecked (i)->getSizeInUnits(); transactions.removeLast(); transactionNames.remove (transactionNames.size() - 1); } while (nextIndex > 0 && totalUnitsStored > maxNumUnitsToKeep && transactions.size() > minimumTransactionsToKeep) { const OwnedArray * const firstSet = transactions.getFirst(); for (int i = firstSet->size(); --i >= 0;) totalUnitsStored -= firstSet->getUnchecked (i)->getSizeInUnits(); jassert (totalUnitsStored >= 0); // something fishy going on if this fails! transactions.remove (0); transactionNames.remove (0); --nextIndex; } sendChangeMessage(); return true; } } return false; } void UndoManager::beginNewTransaction (const String& actionName) { newTransaction = true; currentTransactionName = actionName; } void UndoManager::setCurrentTransactionName (const String& newName) { currentTransactionName = newName; } bool UndoManager::canUndo() const { return nextIndex > 0; } bool UndoManager::canRedo() const { return nextIndex < transactions.size(); } String UndoManager::getUndoDescription() const { return transactionNames [nextIndex - 1]; } String UndoManager::getRedoDescription() const { return transactionNames [nextIndex]; } bool UndoManager::undo() { const OwnedArray* const commandSet = transactions [nextIndex - 1]; if (commandSet == nullptr) return false; bool failed = false; { const ScopedValueSetter setter (reentrancyCheck, true); for (int i = commandSet->size(); --i >= 0;) { if (! commandSet->getUnchecked(i)->undo()) { jassertfalse; failed = true; break; } } } if (failed) clearUndoHistory(); else --nextIndex; beginNewTransaction(); sendChangeMessage(); return true; } bool UndoManager::redo() { const OwnedArray* const commandSet = transactions [nextIndex]; if (commandSet == nullptr) return false; bool failed = false; { const ScopedValueSetter setter (reentrancyCheck, true); for (int i = 0; i < commandSet->size(); ++i) { if (! commandSet->getUnchecked(i)->perform()) { jassertfalse; failed = true; break; } } } if (failed) clearUndoHistory(); else ++nextIndex; beginNewTransaction(); sendChangeMessage(); return true; } bool UndoManager::undoCurrentTransactionOnly() { return newTransaction ? false : undo(); } void UndoManager::getActionsInCurrentTransaction (Array & actionsFound) const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) { for (int i = 0; i < commandSet->size(); ++i) actionsFound.add (commandSet->getUnchecked(i)); } } int UndoManager::getNumActionsInCurrentTransaction() const { const OwnedArray * const commandSet = transactions [nextIndex - 1]; if (commandSet != nullptr && ! newTransaction) return commandSet->size(); return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_UndoManager.cpp ***/ /*** Start of inlined file: juce_UnitTest.cpp ***/ BEGIN_JUCE_NAMESPACE UnitTest::UnitTest (const String& name_) : name (name_), runner (nullptr) { getAllTests().add (this); } UnitTest::~UnitTest() { getAllTests().removeValue (this); } Array& UnitTest::getAllTests() { static Array tests; return tests; } void UnitTest::initialise() {} void UnitTest::shutdown() {} void UnitTest::performTest (UnitTestRunner* const runner_) { jassert (runner_ != nullptr); runner = runner_; initialise(); runTest(); shutdown(); } void UnitTest::logMessage (const String& message) { runner->logMessage (message); } void UnitTest::beginTest (const String& testName) { runner->beginNewTest (this, testName); } void UnitTest::expect (const bool result, const String& failureMessage) { if (result) runner->addPass(); else runner->addFail (failureMessage); } UnitTestRunner::UnitTestRunner() : currentTest (nullptr), assertOnFailure (false) { } UnitTestRunner::~UnitTestRunner() { } int UnitTestRunner::getNumResults() const noexcept { return results.size(); } const UnitTestRunner::TestResult* UnitTestRunner::getResult (int index) const noexcept { return results [index]; } void UnitTestRunner::resultsUpdated() { } void UnitTestRunner::runTests (const Array& tests, const bool assertOnFailure_) { results.clear(); assertOnFailure = assertOnFailure_; resultsUpdated(); for (int i = 0; i < tests.size(); ++i) { try { tests.getUnchecked(i)->performTest (this); } catch (...) { addFail ("An unhandled exception was thrown!"); } } endTest(); } void UnitTestRunner::runAllTests (const bool assertOnFailure_) { runTests (UnitTest::getAllTests(), assertOnFailure_); } void UnitTestRunner::logMessage (const String& message) { Logger::writeToLog (message); } void UnitTestRunner::beginNewTest (UnitTest* const test, const String& subCategory) { endTest(); currentTest = test; TestResult* const r = new TestResult(); r->unitTestName = test->getName(); r->subcategoryName = subCategory; r->passes = 0; r->failures = 0; results.add (r); logMessage ("-----------------------------------------------------------------"); logMessage ("Starting test: " + r->unitTestName + " / " + subCategory + "..."); resultsUpdated(); } void UnitTestRunner::endTest() { if (results.size() > 0) { TestResult* const r = results.getLast(); if (r->failures > 0) { String m ("FAILED!! "); m << r->failures << (r->failures == 1 ? " test" : " tests") << " failed, out of a total of " << (r->passes + r->failures); logMessage (String::empty); logMessage (m); logMessage (String::empty); } else { logMessage ("All tests completed successfully"); } } } void UnitTestRunner::addPass() { { const ScopedLock sl (results.getLock()); TestResult* const r = results.getLast(); jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! r->passes++; String message ("Test "); message << (r->failures + r->passes) << " passed"; logMessage (message); } resultsUpdated(); } void UnitTestRunner::addFail (const String& failureMessage) { { const ScopedLock sl (results.getLock()); TestResult* const r = results.getLast(); jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! r->failures++; String message ("!!! Test "); message << (r->failures + r->passes) << " failed"; if (failureMessage.isNotEmpty()) message << ": " << failureMessage; r->messages.add (message); logMessage (message); } resultsUpdated(); if (assertOnFailure) { jassertfalse } } END_JUCE_NAMESPACE /*** End of inlined file: juce_UnitTest.cpp ***/ /*** Start of inlined file: juce_DeletedAtShutdown.cpp ***/ BEGIN_JUCE_NAMESPACE static SpinLock deletedAtShutdownLock; DeletedAtShutdown::DeletedAtShutdown() { const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().add (this); } DeletedAtShutdown::~DeletedAtShutdown() { const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().removeValue (this); } void DeletedAtShutdown::deleteAll() { // make a local copy of the array, so it can't get into a loop if something // creates another DeletedAtShutdown object during its destructor. Array localCopy; { const SpinLock::ScopedLockType sl (deletedAtShutdownLock); localCopy = getObjects(); } for (int i = localCopy.size(); --i >= 0;) { JUCE_TRY { DeletedAtShutdown* deletee = localCopy.getUnchecked(i); // double-check that it's not already been deleted during another object's destructor. { const SpinLock::ScopedLockType sl (deletedAtShutdownLock); if (! getObjects().contains (deletee)) deletee = nullptr; } delete deletee; } JUCE_CATCH_EXCEPTION } // if no objects got re-created during shutdown, this should have been emptied by their // destructors jassert (getObjects().size() == 0); getObjects().clear(); // just to make sure the array doesn't have any memory still allocated } Array & DeletedAtShutdown::getObjects() { static Array objects; return objects; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DeletedAtShutdown.cpp ***/ /*** Start of inlined file: juce_AiffAudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE static const char* const aiffFormatName = "AIFF file"; static const char* const aiffExtensions[] = { ".aiff", ".aif", 0 }; namespace AiffFileHelpers { inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } #if JUCE_MSVC #pragma pack (push, 1) #define PACKED #elif JUCE_GCC #define PACKED __attribute__((packed)) #else #define PACKED #endif struct InstChunk { struct Loop { uint16 type; // these are different in AIFF and WAV uint16 startIdentifier; uint16 endIdentifier; } PACKED; int8 baseNote; int8 detune; int8 lowNote; int8 highNote; int8 lowVelocity; int8 highVelocity; int16 gain; Loop sustainLoop; Loop releaseLoop; void copyTo (StringPairArray& values) const { values.set ("MidiUnityNote", String (baseNote)); values.set ("Detune", String (detune)); values.set ("LowNote", String (lowNote)); values.set ("HighNote", String (highNote)); values.set ("LowVelocity", String (lowVelocity)); values.set ("HighVelocity", String (highVelocity)); values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); } static void create (MemoryBlock& block, const StringPairArray& values) { if (values.getAllKeys().contains ("MidiUnityNote", true)) { block.setSize ((sizeof (InstChunk) + 3) & ~3, true); InstChunk* const inst = static_cast (block.getData()); inst->baseNote = (int8) values.getValue ("MidiUnityNote", "60").getIntValue(); inst->detune = (int8) values.getValue ("Detune", "0").getIntValue(); inst->lowNote = (int8) values.getValue ("LowNote", "0").getIntValue(); inst->highNote = (int8) values.getValue ("HighNote", "127").getIntValue(); inst->lowVelocity = (int8) values.getValue ("LowVelocity", "1").getIntValue(); inst->highVelocity = (int8) values.getValue ("HighVelocity", "127").getIntValue(); inst->gain = (int16) ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Gain", "0").getIntValue()); inst->sustainLoop.type = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0Type", "0").getIntValue()); inst->sustainLoop.startIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0StartIdentifier", "0").getIntValue()); inst->sustainLoop.endIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0EndIdentifier", "0").getIntValue()); inst->releaseLoop.type = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1Type", "0").getIntValue()); inst->releaseLoop.startIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1StartIdentifier", "0").getIntValue()); inst->releaseLoop.endIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1EndIdentifier", "0").getIntValue()); } } } PACKED; #if JUCE_MSVC #pragma pack (pop) #endif #undef PACKED namespace MarkChunk { bool metaDataContainsZeroIdentifiers (const StringPairArray& values) { // (zero cue identifiers are valid for WAV but not for AIFF) const String cueString ("Cue"); const String noteString ("CueNote"); const String identifierString ("Identifier"); const StringArray& keys = values.getAllKeys(); for (int i = 0; i < keys.size(); ++i) { const String key (keys[i]); if (key.startsWith (noteString)) continue; // zero identifier IS valid in a COMT chunk if (key.startsWith (cueString) && key.contains (identifierString)) { const int value = values.getValue (key, "-1").getIntValue(); if (value == 0) return true; } } return false; } void create (MemoryBlock& block, const StringPairArray& values) { const int numCues = values.getValue ("NumCuePoints", "0").getIntValue(); if (numCues > 0) { MemoryOutputStream out (block, false); out.writeShortBigEndian ((short) numCues); const int numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue(); const int idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF #if JUCE_DEBUG Array identifiers; #endif for (int i = 0; i < numCues; ++i) { const String prefixCue ("Cue" + String (i)); const int identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue(); #if JUCE_DEBUG jassert (! identifiers.contains (identifier)); identifiers.add (identifier); #endif const int offset = values.getValue (prefixCue + "Offset", "0").getIntValue(); String label ("CueLabel" + String (i)); for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex) { const String prefixLabel ("CueLabel" + String (labelIndex)); const int labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue(); if (labelIdentifier == identifier) { label = values.getValue (prefixLabel + "Text", label); break; } } out.writeShortBigEndian ((short) identifier); out.writeIntBigEndian (offset); const int labelLength = jmin (254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring out.writeByte ((char) labelLength + 1); out.write (label.toUTF8(), labelLength); out.writeByte (0); } if ((out.getDataSize() & 1) != 0) out.writeByte (0); } } } namespace COMTChunk { void create (MemoryBlock& block, const StringPairArray& values) { const int numNotes = values.getValue ("NumCueNotes", "0").getIntValue(); if (numNotes > 0) { MemoryOutputStream out (block, false); out.writeShortBigEndian ((short) numNotes); for (int i = 0; i < numNotes; ++i) { const String prefix ("CueNote" + String (i)); out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue()); out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue()); const String comment (values.getValue (prefix + "Text", String::empty)); out.write (comment.toUTF8(), jmin (comment.getNumBytesAsUTF8(), 65534)); out.writeByte (0); if ((out.getDataSize() & 1) != 0) out.writeByte (0); } } } } } class AiffAudioFormatReader : public AudioFormatReader { public: int bytesPerFrame; int64 dataChunkStart; bool littleEndian; AiffAudioFormatReader (InputStream* in) : AudioFormatReader (in, TRANS (aiffFormatName)) { using namespace AiffFileHelpers; if (input->readInt() == chunkName ("FORM")) { const int len = input->readIntBigEndian(); const int64 end = input->getPosition() + len; const int nextType = input->readInt(); if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC")) { bool hasGotVer = false; bool hasGotData = false; bool hasGotType = false; while (input->getPosition() < end) { const int type = input->readInt(); const uint32 length = (uint32) input->readIntBigEndian(); const int64 chunkEnd = input->getPosition() + length; if (type == chunkName ("FVER")) { hasGotVer = true; const int ver = input->readIntBigEndian(); if (ver != 0 && ver != (int) 0xa2805140) break; } else if (type == chunkName ("COMM")) { hasGotType = true; numChannels = (unsigned int) input->readShortBigEndian(); lengthInSamples = input->readIntBigEndian(); bitsPerSample = input->readShortBigEndian(); bytesPerFrame = (numChannels * bitsPerSample) >> 3; unsigned char sampleRateBytes[10]; input->read (sampleRateBytes, 10); const int byte0 = sampleRateBytes[0]; if ((byte0 & 0x80) != 0 || byte0 <= 0x3F || byte0 > 0x40 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C)) break; unsigned int sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2); sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes)); sampleRate = (int) sampRate; if (length <= 18) { // some types don't have a chunk large enough to include a compression // type, so assume it's just big-endian pcm littleEndian = false; } else { const int compType = input->readInt(); if (compType == chunkName ("NONE") || compType == chunkName ("twos")) { littleEndian = false; } else if (compType == chunkName ("sowt")) { littleEndian = true; } else { sampleRate = 0; break; } } } else if (type == chunkName ("SSND")) { hasGotData = true; const int offset = input->readIntBigEndian(); dataChunkStart = input->getPosition() + 4 + offset; lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, (int64) (length / bytesPerFrame)) : 0; } else if (type == chunkName ("MARK")) { const uint16 numCues = (uint16) input->readShortBigEndian(); // these two are always the same for AIFF-read files metadataValues.set ("NumCuePoints", String (numCues)); metadataValues.set ("NumCueLabels", String (numCues)); for (uint16 i = 0; i < numCues; ++i) { uint16 identifier = (uint16) input->readShortBigEndian(); uint32 offset = (uint32) input->readIntBigEndian(); uint8 stringLength = (uint8) input->readByte(); MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength); // if the stringLength is even then read one more byte as the // string needs to be an even number of bytes INCLUDING the // leading length character in the pascal string if ((stringLength & 1) == 0) input->readByte(); const String text = String::fromUTF8 ((const char*)textBlock.getData(), stringLength); const String prefixCue ("Cue" + String (i)); metadataValues.set (prefixCue + "Identifier", String (identifier)); metadataValues.set (prefixCue + "Offset", String (offset)); const String prefixLabel ("CueLabel" + String (i)); metadataValues.set (prefixLabel + "Identifier", String (identifier)); metadataValues.set (prefixLabel + "Text", text); } } else if (type == chunkName ("COMT")) { const uint16 numNotes = (uint16) input->readShortBigEndian(); metadataValues.set ("NumCueNotes", String (numNotes)); for (uint16 i = 0; i < numNotes; ++i) { uint32 timestamp = (uint32) input->readIntBigEndian(); uint16 identifier = (uint16) input->readShortBigEndian(); // may be zero in this case uint16 stringLength = (uint16) input->readShortBigEndian(); MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); const String text = String::fromUTF8 ((const char*)textBlock.getData(), stringLength); const String prefix ("CueNote" + String (i)); metadataValues.set (prefix + "TimeStamp", String (timestamp)); metadataValues.set (prefix + "Identifier", String (identifier)); metadataValues.set (prefix + "Text", text); } } else if (type == chunkName ("INST")) { HeapBlock inst; inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); input->read (inst, length); inst->copyTo (metadataValues); } else if ((hasGotVer && hasGotData && hasGotType) || chunkEnd < input->getPosition() || input->isExhausted()) { break; } input->setPosition (chunkEnd); } } } if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "AIFF"); } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { const int64 samplesAvailable = lengthInSamples - startSampleInFile; if (samplesAvailable < numSamples) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != nullptr) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); numSamples = (int) samplesAvailable; } if (numSamples <= 0) return true; input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); while (numSamples > 0) { const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) char tempBuffer [tempBufSize]; const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); if (bytesRead < numThisTime * bytesPerFrame) { jassert (bytesRead >= 0); zeromem (tempBuffer + bytesRead, numThisTime * bytesPerFrame - bytesRead); } jassert (! usesFloatingPointData); // (would need to add support for this if it's possible) if (littleEndian) { switch (bitsPerSample) { case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 32: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; default: jassertfalse; break; } } else { switch (bitsPerSample) { case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 32: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; default: jassertfalse; break; } } startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; } return true; } private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader); }; class AiffAudioFormatWriter : public AudioFormatWriter { public: AiffAudioFormatWriter (OutputStream* out, double sampleRate_, unsigned int numChans, int bits, const StringPairArray& metadataValues) : AudioFormatWriter (out, TRANS (aiffFormatName), sampleRate_, numChans, bits), lengthInSamples (0), bytesWritten (0), writeFailed (false) { using namespace AiffFileHelpers; if (metadataValues.size() > 0) { // The meta data should have been santised for the AIFF format. // If it was originally sourced from a WAV file the MetaDataSource // key should be removed (or set to "AIFF") once this has been done jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV"); MarkChunk::create (markChunk, metadataValues); COMTChunk::create (comtChunk, metadataValues); InstChunk::create (instChunk, metadataValues); } headerPosition = out->getPosition(); writeHeader(); } ~AiffAudioFormatWriter() { if ((bytesWritten & 1) != 0) output->writeByte (0); writeHeader(); } bool write (const int** data, int numSamples) { jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! if (writeFailed) return false; const int bytes = numChannels * numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); switch (bitsPerSample) { case 8: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 16: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 24: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 32: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; default: jassertfalse; break; } if (bytesWritten + bytes >= (uint32) 0xfff00000 || ! output->write (tempBlock.getData(), bytes)) { // failed to write to disk, so let's try writing the header. // If it's just run out of disk space, then if it does manage // to write the header, we'll still have a useable file.. writeHeader(); writeFailed = true; return false; } else { bytesWritten += bytes; lengthInSamples += numSamples; return true; } } private: MemoryBlock tempBlock, markChunk, comtChunk, instChunk; uint32 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; void writeHeader() { using namespace AiffFileHelpers; const bool couldSeekOk = output->setPosition (headerPosition); (void) couldSeekOk; // if this fails, you've given it an output stream that can't seek! It needs // to be able to seek back to write the header jassert (couldSeekOk); const int headerLen = 54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0) + (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0) + (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0); int audioBytes = lengthInSamples * ((bitsPerSample * numChannels) / 8); audioBytes += (audioBytes & 1); output->writeInt (chunkName ("FORM")); output->writeIntBigEndian (headerLen + audioBytes - 8); output->writeInt (chunkName ("AIFF")); output->writeInt (chunkName ("COMM")); output->writeIntBigEndian (18); output->writeShortBigEndian ((short) numChannels); output->writeIntBigEndian (lengthInSamples); output->writeShortBigEndian ((short) bitsPerSample); uint8 sampleRateBytes[10] = { 0 }; if (sampleRate <= 1) { sampleRateBytes[0] = 0x3f; sampleRateBytes[1] = 0xff; sampleRateBytes[2] = 0x80; } else { int mask = 0x40000000; sampleRateBytes[0] = 0x40; if (sampleRate >= mask) { jassertfalse; sampleRateBytes[1] = 0x1d; } else { int n = (int) sampleRate; int i; for (i = 0; i <= 32 ; ++i) { if ((n & mask) != 0) break; mask >>= 1; } n = n << (i + 1); sampleRateBytes[1] = (uint8) (29 - i); sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff); sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff); sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff); sampleRateBytes[5] = (uint8) (n & 0xff); } } output->write (sampleRateBytes, 10); if (markChunk.getSize() > 0) { output->writeInt (chunkName ("MARK")); output->writeIntBigEndian ((int) markChunk.getSize()); output->write (markChunk.getData(), (int) markChunk.getSize()); } if (comtChunk.getSize() > 0) { output->writeInt (chunkName ("COMT")); output->writeIntBigEndian ((int) comtChunk.getSize()); output->write (comtChunk.getData(), (int) comtChunk.getSize()); } if (instChunk.getSize() > 0) { output->writeInt (chunkName ("INST")); output->writeIntBigEndian ((int) instChunk.getSize()); output->write (instChunk.getData(), (int) instChunk.getSize()); } output->writeInt (chunkName ("SSND")); output->writeIntBigEndian (audioBytes + 8); output->writeInt (0); output->writeInt (0); jassert (output->getPosition() == headerLen); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter); }; AiffAudioFormat::AiffAudioFormat() : AudioFormat (TRANS (aiffFormatName), StringArray (aiffExtensions)) { } AiffAudioFormat::~AiffAudioFormat() { } const Array AiffAudioFormat::getPossibleSampleRates() { const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; return Array (rates); } const Array AiffAudioFormat::getPossibleBitDepths() { const int depths[] = { 8, 16, 24, 0 }; return Array (depths); } bool AiffAudioFormat::canDoStereo() { return true; } bool AiffAudioFormat::canDoMono() { return true; } #if JUCE_MAC bool AiffAudioFormat::canHandleFile (const File& f) { if (AudioFormat::canHandleFile (f)) return true; const OSType type = PlatformUtilities::getTypeOfFile (f.getFullPathName()); return type == 'AIFF' || type == 'AIFC' || type == 'aiff' || type == 'aifc'; } #endif AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer w (new AiffAudioFormatReader (sourceStream)); if (w->sampleRate > 0) return w.release(); if (! deleteStreamIfOpeningFails) w->input = nullptr; return nullptr; } AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int /*qualityOptionIndex*/) { if (getPossibleBitDepths().contains (bitsPerSample)) return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, bitsPerSample, metadataValues); return nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AiffAudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormat::AudioFormat (const String& name, const StringArray& extensions) : formatName (name), fileExtensions (extensions) { } AudioFormat::~AudioFormat() { } bool AudioFormat::canHandleFile (const File& f) { for (int i = 0; i < fileExtensions.size(); ++i) if (f.hasFileExtension (fileExtensions[i])) return true; return false; } const String& AudioFormat::getFormatName() const { return formatName; } const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } bool AudioFormat::isCompressed() { return false; } const StringArray AudioFormat::getQualityOptions() { return StringArray(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioFormatReader.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatReader::AudioFormatReader (InputStream* const in, const String& formatName_) : sampleRate (0), bitsPerSample (0), lengthInSamples (0), numChannels (0), usesFloatingPointData (false), input (in), formatName (formatName_) { } AudioFormatReader::~AudioFormatReader() { delete input; } bool AudioFormatReader::read (int* const* destSamples, int numDestChannels, int64 startSampleInSource, int numSamplesToRead, const bool fillLeftoverChannelsWithCopies) { jassert (numDestChannels > 0); // you have to actually give this some channels to work with! int startOffsetInDestBuffer = 0; if (startSampleInSource < 0) { const int silence = (int) jmin (-startSampleInSource, (int64) numSamplesToRead); for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != nullptr) zeromem (destSamples[i], sizeof (int) * silence); startOffsetInDestBuffer += silence; numSamplesToRead -= silence; startSampleInSource = 0; } if (numSamplesToRead <= 0) return true; if (! readSamples (const_cast (destSamples), jmin ((int) numChannels, numDestChannels), startOffsetInDestBuffer, startSampleInSource, numSamplesToRead)) return false; if (numDestChannels > (int) numChannels) { if (fillLeftoverChannelsWithCopies) { int* lastFullChannel = destSamples[0]; for (int i = (int) numChannels; --i > 0;) { if (destSamples[i] != nullptr) { lastFullChannel = destSamples[i]; break; } } if (lastFullChannel != nullptr) for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != nullptr) memcpy (destSamples[i], lastFullChannel, sizeof (int) * numSamplesToRead); } else { for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != nullptr) zeromem (destSamples[i], sizeof (int) * numSamplesToRead); } } return true; } void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, float& lowestRight, float& highestRight) { if (numSamples <= 0) { lowestLeft = 0; lowestRight = 0; highestLeft = 0; highestRight = 0; return; } const int bufferSize = (int) jmin (numSamples, (int64) 4096); HeapBlock tempSpace (bufferSize * 2 + 64); int* tempBuffer[3]; tempBuffer[0] = tempSpace.getData(); tempBuffer[1] = tempSpace.getData() + bufferSize; tempBuffer[2] = 0; if (usesFloatingPointData) { float lmin = 1.0e6f; float lmax = -lmin; float rmin = lmin; float rmax = lmax; while (numSamples > 0) { const int numToDo = (int) jmin (numSamples, (int64) bufferSize); read (tempBuffer, 2, startSampleInFile, numToDo, false); numSamples -= numToDo; startSampleInFile += numToDo; float bufMin, bufMax; findMinAndMax (reinterpret_cast (tempBuffer[0]), numToDo, bufMin, bufMax); lmin = jmin (lmin, bufMin); lmax = jmax (lmax, bufMax); if (numChannels > 1) { findMinAndMax (reinterpret_cast (tempBuffer[1]), numToDo, bufMin, bufMax); rmin = jmin (rmin, bufMin); rmax = jmax (rmax, bufMax); } } if (numChannels <= 1) { rmax = lmax; rmin = lmin; } lowestLeft = lmin; highestLeft = lmax; lowestRight = rmin; highestRight = rmax; } else { int lmax = std::numeric_limits::min(); int lmin = std::numeric_limits::max(); int rmax = std::numeric_limits::min(); int rmin = std::numeric_limits::max(); while (numSamples > 0) { const int numToDo = (int) jmin (numSamples, (int64) bufferSize); if (! read (tempBuffer, 2, startSampleInFile, numToDo, false)) break; numSamples -= numToDo; startSampleInFile += numToDo; for (int j = numChannels; --j >= 0;) { int bufMin, bufMax; findMinAndMax (tempBuffer[j], numToDo, bufMin, bufMax); if (j == 0) { lmax = jmax (lmax, bufMax); lmin = jmin (lmin, bufMin); } else { rmax = jmax (rmax, bufMax); rmin = jmin (rmin, bufMin); } } } if (numChannels <= 1) { rmax = lmax; rmin = lmin; } lowestLeft = lmin / (float) std::numeric_limits::max(); highestLeft = lmax / (float) std::numeric_limits::max(); lowestRight = rmin / (float) std::numeric_limits::max(); highestRight = rmax / (float) std::numeric_limits::max(); } } int64 AudioFormatReader::searchForLevel (int64 startSample, int64 numSamplesToSearch, const double magnitudeRangeMinimum, const double magnitudeRangeMaximum, const int minimumConsecutiveSamples) { if (numSamplesToSearch == 0) return -1; const int bufferSize = 4096; HeapBlock tempSpace (bufferSize * 2 + 64); int* tempBuffer[3]; tempBuffer[0] = tempSpace.getData(); tempBuffer[1] = tempSpace.getData() + bufferSize; tempBuffer[2] = 0; int consecutive = 0; int64 firstMatchPos = -1; jassert (magnitudeRangeMaximum > magnitudeRangeMinimum); const double doubleMin = jlimit (0.0, (double) std::numeric_limits::max(), magnitudeRangeMinimum * std::numeric_limits::max()); const double doubleMax = jlimit (doubleMin, (double) std::numeric_limits::max(), magnitudeRangeMaximum * std::numeric_limits::max()); const int intMagnitudeRangeMinimum = roundToInt (doubleMin); const int intMagnitudeRangeMaximum = roundToInt (doubleMax); while (numSamplesToSearch != 0) { const int numThisTime = (int) jmin (abs64 (numSamplesToSearch), (int64) bufferSize); int64 bufferStart = startSample; if (numSamplesToSearch < 0) bufferStart -= numThisTime; if (bufferStart >= (int) lengthInSamples) break; read (tempBuffer, 2, bufferStart, numThisTime, false); int num = numThisTime; while (--num >= 0) { if (numSamplesToSearch < 0) --startSample; bool matches = false; const int index = (int) (startSample - bufferStart); if (usesFloatingPointData) { const float sample1 = std::abs (((float*) tempBuffer[0]) [index]); if (sample1 >= magnitudeRangeMinimum && sample1 <= magnitudeRangeMaximum) { matches = true; } else if (numChannels > 1) { const float sample2 = std::abs (((float*) tempBuffer[1]) [index]); matches = (sample2 >= magnitudeRangeMinimum && sample2 <= magnitudeRangeMaximum); } } else { const int sample1 = abs (tempBuffer[0] [index]); if (sample1 >= intMagnitudeRangeMinimum && sample1 <= intMagnitudeRangeMaximum) { matches = true; } else if (numChannels > 1) { const int sample2 = abs (tempBuffer[1][index]); matches = (sample2 >= intMagnitudeRangeMinimum && sample2 <= intMagnitudeRangeMaximum); } } if (matches) { if (firstMatchPos < 0) firstMatchPos = startSample; if (++consecutive >= minimumConsecutiveSamples) { if (firstMatchPos < 0 || firstMatchPos >= lengthInSamples) return -1; return firstMatchPos; } } else { consecutive = 0; firstMatchPos = -1; } if (numSamplesToSearch > 0) ++startSample; } if (numSamplesToSearch > 0) numSamplesToSearch -= numThisTime; else numSamplesToSearch += numThisTime; } return -1; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatReader.cpp ***/ /*** Start of inlined file: juce_AudioFormatWriter.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatWriter::AudioFormatWriter (OutputStream* const out, const String& formatName_, const double rate, const unsigned int numChannels_, const unsigned int bitsPerSample_) : sampleRate (rate), numChannels (numChannels_), bitsPerSample (bitsPerSample_), usesFloatingPointData (false), output (out), formatName (formatName_) { } AudioFormatWriter::~AudioFormatWriter() { delete output; } bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, int64 startSample, int64 numSamplesToRead) { const int bufferSize = 16384; AudioSampleBuffer tempBuffer (numChannels, bufferSize); int* buffers [128] = { 0 }; for (int i = tempBuffer.getNumChannels(); --i >= 0;) buffers[i] = reinterpret_cast (tempBuffer.getSampleData (i, 0)); if (numSamplesToRead < 0) numSamplesToRead = reader.lengthInSamples; while (numSamplesToRead > 0) { const int numToDo = (int) jmin (numSamplesToRead, (int64) bufferSize); if (! reader.read (buffers, numChannels, startSample, numToDo, false)) return false; if (reader.usesFloatingPointData != isFloatingPoint()) { int** bufferChan = buffers; while (*bufferChan != nullptr) { int* b = *bufferChan++; if (isFloatingPoint()) { // int -> float const double factor = 1.0 / std::numeric_limits::max(); for (int i = 0; i < numToDo; ++i) reinterpret_cast (b)[i] = (float) (factor * b[i]); } else { // float -> int for (int i = 0; i < numToDo; ++i) { const double samp = *(const float*) b; if (samp <= -1.0) *b++ = std::numeric_limits::min(); else if (samp >= 1.0) *b++ = std::numeric_limits::max(); else *b++ = roundToInt (std::numeric_limits::max() * samp); } } } } if (! write (const_cast (buffers), numToDo)) return false; numSamplesToRead -= numToDo; startSample += numToDo; } return true; } bool AudioFormatWriter::writeFromAudioSource (AudioSource& source, int numSamplesToRead, const int samplesPerBlock) { AudioSampleBuffer tempBuffer (getNumChannels(), samplesPerBlock); while (numSamplesToRead > 0) { const int numToDo = jmin (numSamplesToRead, samplesPerBlock); AudioSourceChannelInfo info; info.buffer = &tempBuffer; info.startSample = 0; info.numSamples = numToDo; info.clearActiveBufferRegion(); source.getNextAudioBlock (info); if (! writeFromAudioSampleBuffer (tempBuffer, 0, numToDo)) return false; numSamplesToRead -= numToDo; } return true; } bool AudioFormatWriter::writeFromAudioSampleBuffer (const AudioSampleBuffer& source, int startSample, int numSamples) { jassert (startSample >= 0 && startSample + numSamples <= source.getNumSamples() && source.getNumChannels() > 0); if (numSamples <= 0) return true; HeapBlock tempBuffer; HeapBlock chans (numChannels + 1); chans [numChannels] = 0; if (isFloatingPoint()) { for (int i = numChannels; --i >= 0;) chans[i] = reinterpret_cast (source.getSampleData (i, startSample)); } else { tempBuffer.malloc (numSamples * numChannels); for (unsigned int i = 0; i < numChannels; ++i) { typedef AudioData::Pointer DestSampleType; typedef AudioData::Pointer SourceSampleType; DestSampleType destData (chans[i] = tempBuffer + i * numSamples); SourceSampleType sourceData (source.getSampleData (i, startSample)); destData.convertSamples (sourceData, numSamples); } } return write ((const int**) chans.getData(), numSamples); } class AudioFormatWriter::ThreadedWriter::Buffer : public TimeSliceClient, public AbstractFifo { public: Buffer (TimeSliceThread& timeSliceThread_, AudioFormatWriter* writer_, int numChannels, int bufferSize_) : AbstractFifo (bufferSize_), buffer (numChannels, bufferSize_), timeSliceThread (timeSliceThread_), writer (writer_), thumbnailToUpdate (nullptr), samplesWritten (0), isRunning (true) { timeSliceThread.addTimeSliceClient (this); } ~Buffer() { isRunning = false; timeSliceThread.removeTimeSliceClient (this); while (writePendingData() == 0) {} } bool write (const float** data, int numSamples) { if (numSamples <= 0 || ! isRunning) return true; jassert (timeSliceThread.isThreadRunning()); // you need to get your thread running before pumping data into this! int start1, size1, start2, size2; prepareToWrite (numSamples, start1, size1, start2, size2); if (size1 + size2 < numSamples) return false; for (int i = buffer.getNumChannels(); --i >= 0;) { buffer.copyFrom (i, start1, data[i], size1); buffer.copyFrom (i, start2, data[i] + size1, size2); } finishedWrite (size1 + size2); timeSliceThread.notify(); return true; } int useTimeSlice() { return writePendingData(); } int writePendingData() { const int numToDo = getTotalSize() / 4; int start1, size1, start2, size2; prepareToRead (numToDo, start1, size1, start2, size2); if (size1 <= 0) return 10; writer->writeFromAudioSampleBuffer (buffer, start1, size1); const ScopedLock sl (thumbnailLock); if (thumbnailToUpdate != nullptr) thumbnailToUpdate->addBlock (samplesWritten, buffer, start1, size1); samplesWritten += size1; if (size2 > 0) { writer->writeFromAudioSampleBuffer (buffer, start2, size2); if (thumbnailToUpdate != nullptr) thumbnailToUpdate->addBlock (samplesWritten, buffer, start2, size2); samplesWritten += size2; } finishedRead (size1 + size2); return 0; } void setThumbnail (AudioThumbnail* thumb) { if (thumb != nullptr) thumb->reset (buffer.getNumChannels(), writer->getSampleRate(), 0); const ScopedLock sl (thumbnailLock); thumbnailToUpdate = thumb; samplesWritten = 0; } private: AudioSampleBuffer buffer; TimeSliceThread& timeSliceThread; ScopedPointer writer; CriticalSection thumbnailLock; AudioThumbnail* thumbnailToUpdate; int64 samplesWritten; volatile bool isRunning; JUCE_DECLARE_NON_COPYABLE (Buffer); }; AudioFormatWriter::ThreadedWriter::ThreadedWriter (AudioFormatWriter* writer, TimeSliceThread& backgroundThread, int numSamplesToBuffer) : buffer (new AudioFormatWriter::ThreadedWriter::Buffer (backgroundThread, writer, writer->numChannels, numSamplesToBuffer)) { } AudioFormatWriter::ThreadedWriter::~ThreadedWriter() { } bool AudioFormatWriter::ThreadedWriter::write (const float** data, int numSamples) { return buffer->write (data, numSamples); } void AudioFormatWriter::ThreadedWriter::setThumbnailToUpdate (AudioThumbnail* thumb) { buffer->setThumbnail (thumb); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatWriter.cpp ***/ /*** Start of inlined file: juce_AudioFormatManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatManager::AudioFormatManager() : defaultFormatIndex (0) { } AudioFormatManager::~AudioFormatManager() { } void AudioFormatManager::registerFormat (AudioFormat* newFormat, const bool makeThisTheDefaultFormat) { jassert (newFormat != nullptr); if (newFormat != nullptr) { #if JUCE_DEBUG for (int i = getNumKnownFormats(); --i >= 0;) { if (getKnownFormat (i)->getFormatName() == newFormat->getFormatName()) { jassertfalse; // trying to add the same format twice! } } #endif if (makeThisTheDefaultFormat) defaultFormatIndex = getNumKnownFormats(); knownFormats.add (newFormat); } } void AudioFormatManager::registerBasicFormats() { #if JUCE_MAC registerFormat (new AiffAudioFormat(), true); registerFormat (new WavAudioFormat(), false); #else registerFormat (new WavAudioFormat(), true); registerFormat (new AiffAudioFormat(), false); #endif #if JUCE_USE_FLAC registerFormat (new FlacAudioFormat(), false); #endif #if JUCE_USE_OGGVORBIS registerFormat (new OggVorbisAudioFormat(), false); #endif } void AudioFormatManager::clearFormats() { knownFormats.clear(); defaultFormatIndex = 0; } int AudioFormatManager::getNumKnownFormats() const { return knownFormats.size(); } AudioFormat* AudioFormatManager::getKnownFormat (const int index) const { return knownFormats [index]; } AudioFormat* AudioFormatManager::getDefaultFormat() const { return getKnownFormat (defaultFormatIndex); } AudioFormat* AudioFormatManager::findFormatForFileExtension (const String& fileExtension) const { String e (fileExtension); if (! e.startsWithChar ('.')) e = "." + e; for (int i = 0; i < getNumKnownFormats(); ++i) if (getKnownFormat(i)->getFileExtensions().contains (e, true)) return getKnownFormat(i); return nullptr; } const String AudioFormatManager::getWildcardForAllFormats() const { StringArray allExtensions; int i; for (i = 0; i < getNumKnownFormats(); ++i) allExtensions.addArray (getKnownFormat (i)->getFileExtensions()); allExtensions.trim(); allExtensions.removeEmptyStrings(); String s; for (i = 0; i < allExtensions.size(); ++i) { s << '*'; if (! allExtensions[i].startsWithChar ('.')) s << '.'; s << allExtensions[i]; if (i < allExtensions.size() - 1) s << ';'; } return s; } AudioFormatReader* AudioFormatManager::createReaderFor (const File& file) { // you need to actually register some formats before the manager can // use them to open a file! jassert (getNumKnownFormats() > 0); for (int i = 0; i < getNumKnownFormats(); ++i) { AudioFormat* const af = getKnownFormat(i); if (af->canHandleFile (file)) { InputStream* const in = file.createInputStream(); if (in != nullptr) { AudioFormatReader* const r = af->createReaderFor (in, true); if (r != nullptr) return r; } } } return nullptr; } AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileStream) { // you need to actually register some formats before the manager can // use them to open a file! jassert (getNumKnownFormats() > 0); ScopedPointer in (audioFileStream); if (in != nullptr) { const int64 originalStreamPos = in->getPosition(); for (int i = 0; i < getNumKnownFormats(); ++i) { AudioFormatReader* const r = getKnownFormat(i)->createReaderFor (in, false); if (r != nullptr) { in.release(); return r; } in->setPosition (originalStreamPos); // the stream that is passed-in must be capable of being repositioned so // that all the formats can have a go at opening it. jassert (in->getPosition() == originalStreamPos); } } return nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatManager.cpp ***/ /*** Start of inlined file: juce_AudioSubsectionReader.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSubsectionReader::AudioSubsectionReader (AudioFormatReader* const source_, const int64 startSample_, const int64 length_, const bool deleteSourceWhenDeleted_) : AudioFormatReader (0, source_->getFormatName()), source (source_), startSample (startSample_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_) { length = jmin (jmax ((int64) 0, source->lengthInSamples - startSample), length_); sampleRate = source->sampleRate; bitsPerSample = source->bitsPerSample; lengthInSamples = length; numChannels = source->numChannels; usesFloatingPointData = source->usesFloatingPointData; } AudioSubsectionReader::~AudioSubsectionReader() { if (deleteSourceWhenDeleted) delete source; } bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { if (startSampleInFile + numSamples > length) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != nullptr) zeromem (destSamples[i], sizeof (int) * numSamples); numSamples = jmin (numSamples, (int) (length - startSampleInFile)); if (numSamples <= 0) return true; } return source->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startSampleInFile + startSample, numSamples); } void AudioSubsectionReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, float& lowestRight, float& highestRight) { startSampleInFile = jmax ((int64) 0, startSampleInFile); numSamples = jmax ((int64) 0, jmin (numSamples, length - startSampleInFile)); source->readMaxLevels (startSampleInFile + startSample, numSamples, lowestLeft, highestLeft, lowestRight, highestRight); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSubsectionReader.cpp ***/ /*** Start of inlined file: juce_AudioThumbnail.cpp ***/ BEGIN_JUCE_NAMESPACE struct AudioThumbnail::MinMaxValue { MinMaxValue() noexcept { values[0] = 0; values[1] = 0; } inline void set (const char newMin, const char newMax) noexcept { values[0] = newMin; values[1] = newMax; } inline char getMinValue() const noexcept { return values[0]; } inline char getMaxValue() const noexcept { return values[1]; } inline void setFloat (const float newMin, const float newMax) noexcept { values[0] = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f)); values[1] = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f)); if (values[0] == values[1]) { if (values[1] == 127) values[0]--; else values[1]++; } } inline bool isNonZero() const noexcept { return values[1] > values[0]; } inline int getPeak() const noexcept { return jmax (std::abs ((int) values[0]), std::abs ((int) values[1])); } inline void read (InputStream& input) { input.read (values, 2); } inline void write (OutputStream& output) { output.write (values, 2); } private: char values[2]; }; class AudioThumbnail::LevelDataSource : public TimeSliceClient { public: LevelDataSource (AudioThumbnail& owner_, AudioFormatReader* newReader, int64 hash) : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), hashCode (hash), owner (owner_), reader (newReader) { } LevelDataSource (AudioThumbnail& owner_, InputSource* source_) : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), hashCode (source_->hashCode()), owner (owner_), source (source_) { } ~LevelDataSource() { owner.cache.removeTimeSliceClient (this); } enum { timeBeforeDeletingReader = 1000 }; void initialise (int64 numSamplesFinished_) { const ScopedLock sl (readerLock); numSamplesFinished = numSamplesFinished_; createReader(); if (reader != nullptr) { lengthInSamples = reader->lengthInSamples; numChannels = reader->numChannels; sampleRate = reader->sampleRate; if (lengthInSamples <= 0 || isFullyLoaded()) reader = nullptr; else owner.cache.addTimeSliceClient (this); } } void getLevels (int64 startSample, int numSamples, Array& levels) { const ScopedLock sl (readerLock); if (reader == nullptr) { createReader(); if (reader != nullptr) owner.cache.addTimeSliceClient (this); } if (reader != nullptr) { float l[4] = { 0 }; reader->readMaxLevels (startSample, numSamples, l[0], l[1], l[2], l[3]); levels.clearQuick(); levels.addArray ((const float*) l, 4); } } void releaseResources() { const ScopedLock sl (readerLock); reader = nullptr; } int useTimeSlice() { if (isFullyLoaded()) { if (reader != nullptr && source != nullptr) releaseResources(); return -1; } bool justFinished = false; { const ScopedLock sl (readerLock); createReader(); if (reader != nullptr) { if (! readNextBlock()) return 0; justFinished = true; } } if (justFinished) owner.cache.storeThumb (owner, hashCode); return timeBeforeDeletingReader; } bool isFullyLoaded() const noexcept { return numSamplesFinished >= lengthInSamples; } inline int sampleToThumbSample (const int64 originalSample) const noexcept { return (int) (originalSample / owner.samplesPerThumbSample); } int64 lengthInSamples, numSamplesFinished; double sampleRate; int numChannels; int64 hashCode; private: AudioThumbnail& owner; ScopedPointer source; ScopedPointer reader; CriticalSection readerLock; void createReader() { if (reader == nullptr && source != nullptr) { InputStream* audioFileStream = source->createInputStream(); if (audioFileStream != nullptr) reader = owner.formatManagerToUse.createReaderFor (audioFileStream); } } bool readNextBlock() { jassert (reader != nullptr); if (! isFullyLoaded()) { const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished); if (numToDo > 0) { int64 startSample = numSamplesFinished; const int firstThumbIndex = sampleToThumbSample (startSample); const int lastThumbIndex = sampleToThumbSample (startSample + numToDo); const int numThumbSamps = lastThumbIndex - firstThumbIndex; HeapBlock levelData (numThumbSamps * 2); MinMaxValue* levels[2] = { levelData, levelData + numThumbSamps }; for (int i = 0; i < numThumbSamps; ++i) { float lowestLeft, highestLeft, lowestRight, highestRight; reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample, owner.samplesPerThumbSample, lowestLeft, highestLeft, lowestRight, highestRight); levels[0][i].setFloat (lowestLeft, highestLeft); levels[1][i].setFloat (lowestRight, highestRight); } { const ScopedUnlock su (readerLock); owner.setLevels (levels, firstThumbIndex, 2, numThumbSamps); } numSamplesFinished += numToDo; } } return isFullyLoaded(); } }; class AudioThumbnail::ThumbData { public: ThumbData (const int numThumbSamples) : peakLevel (-1) { ensureSize (numThumbSamples); } inline MinMaxValue* getData (const int thumbSampleIndex) noexcept { jassert (thumbSampleIndex < data.size()); return data.getRawDataPointer() + thumbSampleIndex; } int getSize() const noexcept { return data.size(); } void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept { if (startSample >= 0) { endSample = jmin (endSample, data.size() - 1); char mx = -128; char mn = 127; while (startSample <= endSample) { const MinMaxValue& v = data.getReference (startSample); if (v.getMinValue() < mn) mn = v.getMinValue(); if (v.getMaxValue() > mx) mx = v.getMaxValue(); ++startSample; } if (mn <= mx) { result.set (mn, mx); return; } } result.set (1, 0); } void write (const MinMaxValue* const source, const int startIndex, const int numValues) { resetPeak(); if (startIndex + numValues > data.size()) ensureSize (startIndex + numValues); MinMaxValue* const dest = getData (startIndex); for (int i = 0; i < numValues; ++i) dest[i] = source[i]; } void resetPeak() noexcept { peakLevel = -1; } int getPeak() noexcept { if (peakLevel < 0) { for (int i = 0; i < data.size(); ++i) { const int peak = data[i].getPeak(); if (peak > peakLevel) peakLevel = peak; } } return peakLevel; } private: Array data; int peakLevel; void ensureSize (const int thumbSamples) { const int extraNeeded = thumbSamples - data.size(); if (extraNeeded > 0) data.insertMultiple (-1, MinMaxValue(), extraNeeded); } }; class AudioThumbnail::CachedWindow { public: CachedWindow() : cachedStart (0), cachedTimePerPixel (0), numChannelsCached (0), numSamplesCached (0), cacheNeedsRefilling (true) { } void invalidate() { cacheNeedsRefilling = true; } void drawChannel (Graphics& g, const Rectangle& area, const double startTime, const double endTime, const int channelNum, const float verticalZoomFactor, const double sampleRate, const int numChannels, const int samplesPerThumbSample, LevelDataSource* levelData, const OwnedArray& channels) { refillCache (area.getWidth(), startTime, endTime, sampleRate, numChannels, samplesPerThumbSample, levelData, channels); if (isPositiveAndBelow (channelNum, numChannelsCached)) { const Rectangle clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())))); if (! clip.isEmpty()) { const float topY = (float) area.getY(); const float bottomY = (float) area.getBottom(); const float midY = (topY + bottomY) * 0.5f; const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f; const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX()); int x = clip.getX(); for (int w = clip.getWidth(); --w >= 0;) { if (cacheData->isNonZero()) g.drawVerticalLine (x, jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY), jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY)); ++x; ++cacheData; } } } } private: Array data; double cachedStart, cachedTimePerPixel; int numChannelsCached, numSamplesCached; bool cacheNeedsRefilling; void refillCache (const int numSamples, double startTime, const double endTime, const double sampleRate, const int numChannels, const int samplesPerThumbSample, LevelDataSource* levelData, const OwnedArray& channels) { const double timePerPixel = (endTime - startTime) / numSamples; if (numSamples <= 0 || timePerPixel <= 0.0 || sampleRate <= 0) { invalidate(); return; } if (numSamples == numSamplesCached && numChannelsCached == numChannels && startTime == cachedStart && timePerPixel == cachedTimePerPixel && ! cacheNeedsRefilling) { return; } numSamplesCached = numSamples; numChannelsCached = numChannels; cachedStart = startTime; cachedTimePerPixel = timePerPixel; cacheNeedsRefilling = false; ensureSize (numSamples); if (timePerPixel * sampleRate <= samplesPerThumbSample && levelData != nullptr) { int sample = roundToInt (startTime * sampleRate); Array levels; int i; for (i = 0; i < numSamples; ++i) { const int nextSample = roundToInt ((startTime + timePerPixel) * sampleRate); if (sample >= 0) { if (sample >= levelData->lengthInSamples) break; levelData->getLevels (sample, jmax (1, nextSample - sample), levels); const int numChans = jmin (levels.size() / 2, numChannelsCached); for (int chan = 0; chan < numChans; ++chan) getData (chan, i)->setFloat (levels.getUnchecked (chan * 2), levels.getUnchecked (chan * 2 + 1)); } startTime += timePerPixel; sample = nextSample; } numSamplesCached = i; } else { jassert (channels.size() == numChannelsCached); for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) { ThumbData* channelData = channels.getUnchecked (channelNum); MinMaxValue* cacheData = getData (channelNum, 0); const double timeToThumbSampleFactor = sampleRate / (double) samplesPerThumbSample; startTime = cachedStart; int sample = roundToInt (startTime * timeToThumbSampleFactor); for (int i = numSamples; --i >= 0;) { const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); channelData->getMinMax (sample, nextSample, *cacheData); ++cacheData; startTime += timePerPixel; sample = nextSample; } } } } MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept { jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size())); return data.getRawDataPointer() + channelNum * numSamplesCached + cacheIndex; } void ensureSize (const int numSamples) { const int itemsRequired = numSamples * numChannelsCached; if (data.size() < itemsRequired) data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size()); } }; AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample, AudioFormatManager& formatManagerToUse_, AudioThumbnailCache& cacheToUse) : formatManagerToUse (formatManagerToUse_), cache (cacheToUse), window (new CachedWindow()), samplesPerThumbSample (originalSamplesPerThumbnailSample), totalSamples (0), numChannels (0), sampleRate (0) { } AudioThumbnail::~AudioThumbnail() { clear(); } void AudioThumbnail::clear() { source = nullptr; clearChannelData(); } void AudioThumbnail::clearChannelData() { const ScopedLock sl (lock); window->invalidate(); channels.clear(); totalSamples = numSamplesFinished = 0; numChannels = 0; sampleRate = 0; sendChangeMessage(); } void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource) { clear(); numChannels = newNumChannels; sampleRate = newSampleRate; totalSamples = totalSamplesInSource; createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample)); } void AudioThumbnail::createChannels (const int length) { while (channels.size() < numChannels) channels.add (new ThumbData (length)); } void AudioThumbnail::loadFrom (InputStream& rawInput) { clearChannelData(); BufferedInputStream input (rawInput, 4096); if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm') return; samplesPerThumbSample = input.readInt(); totalSamples = input.readInt64(); // Total number of source samples. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data. numChannels = input.readInt(); // Number of audio channels. sampleRate = input.readInt(); // Source sample rate. input.skipNextBytes (16); // (reserved) createChannels (numThumbnailSamples); for (int i = 0; i < numThumbnailSamples; ++i) for (int chan = 0; chan < numChannels; ++chan) channels.getUnchecked(chan)->getData(i)->read (input); } void AudioThumbnail::saveTo (OutputStream& output) const { const ScopedLock sl (lock); const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize(); output.write ("jatm", 4); output.writeInt (samplesPerThumbSample); output.writeInt64 (totalSamples); output.writeInt64 (numSamplesFinished); output.writeInt (numThumbnailSamples); output.writeInt (numChannels); output.writeInt ((int) sampleRate); output.writeInt64 (0); output.writeInt64 (0); for (int i = 0; i < numThumbnailSamples; ++i) for (int chan = 0; chan < numChannels; ++chan) channels.getUnchecked(chan)->getData(i)->write (output); } bool AudioThumbnail::setDataSource (LevelDataSource* newSource) { jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); numSamplesFinished = 0; if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded()) { source = newSource; // (make sure this isn't done before loadThumb is called) source->lengthInSamples = totalSamples; source->sampleRate = sampleRate; source->numChannels = numChannels; source->numSamplesFinished = numSamplesFinished; } else { source = newSource; // (make sure this isn't done before loadThumb is called) const ScopedLock sl (lock); source->initialise (numSamplesFinished); totalSamples = source->lengthInSamples; sampleRate = source->sampleRate; numChannels = source->numChannels; createChannels (1 + (int) (totalSamples / samplesPerThumbSample)); } return sampleRate > 0 && totalSamples > 0; } bool AudioThumbnail::setSource (InputSource* const newSource) { clear(); return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource)); } void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash) { clear(); if (newReader != nullptr) setDataSource (new LevelDataSource (*this, newReader, hash)); } int64 AudioThumbnail::getHashCode() const { return source == nullptr ? 0 : source->hashCode; } void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming, int startOffsetInBuffer, int numSamples) { jassert (startSample >= 0); const int firstThumbIndex = (int) (startSample / samplesPerThumbSample); const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample); const int numToDo = lastThumbIndex - firstThumbIndex; if (numToDo > 0) { const int numChans = jmin (channels.size(), incoming.getNumChannels()); const HeapBlock thumbData (numToDo * numChans); const HeapBlock thumbChannels (numChans); for (int chan = 0; chan < numChans; ++chan) { const float* const sourceData = incoming.getSampleData (chan, startOffsetInBuffer); MinMaxValue* const dest = thumbData + numToDo * chan; thumbChannels [chan] = dest; for (int i = 0; i < numToDo; ++i) { float low, high; const int start = i * samplesPerThumbSample; findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start), low, high); dest[i].setFloat (low, high); } } setLevels (thumbChannels, firstThumbIndex, numChans, numToDo); } } void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues) { const ScopedLock sl (lock); for (int i = jmin (numChans, channels.size()); --i >= 0;) channels.getUnchecked(i)->write (values[i], thumbIndex, numValues); const int64 start = thumbIndex * (int64) samplesPerThumbSample; const int64 end = (thumbIndex + numValues) * (int64) samplesPerThumbSample; if (numSamplesFinished >= start && end > numSamplesFinished) numSamplesFinished = end; totalSamples = jmax (numSamplesFinished, totalSamples); window->invalidate(); sendChangeMessage(); } int AudioThumbnail::getNumChannels() const noexcept { return numChannels; } double AudioThumbnail::getTotalLength() const noexcept { return sampleRate > 0 ? (totalSamples / sampleRate) : 0; } bool AudioThumbnail::isFullyLoaded() const noexcept { return numSamplesFinished >= totalSamples - samplesPerThumbSample; } int64 AudioThumbnail::getNumSamplesFinished() const noexcept { return numSamplesFinished; } float AudioThumbnail::getApproximatePeak() const { int peak = 0; for (int i = channels.size(); --i >= 0;) peak = jmax (peak, channels.getUnchecked(i)->getPeak()); return jlimit (0, 127, peak) / 127.0f; } void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex, float& minValue, float& maxValue) const noexcept { MinMaxValue result; const ThumbData* const data = channels [channelIndex]; if (data != nullptr && sampleRate > 0) { const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample); const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample); data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result); } minValue = result.getMinValue() / 128.0f; maxValue = result.getMaxValue() / 128.0f; } void AudioThumbnail::drawChannel (Graphics& g, const Rectangle& area, double startTime, double endTime, int channelNum, float verticalZoomFactor) { const ScopedLock sl (lock); window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor, sampleRate, numChannels, samplesPerThumbSample, source, channels); } void AudioThumbnail::drawChannels (Graphics& g, const Rectangle& area, double startTimeSeconds, double endTimeSeconds, float verticalZoomFactor) { for (int i = 0; i < numChannels; ++i) { const int y1 = roundToInt ((i * area.getHeight()) / numChannels); const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels); drawChannel (g, Rectangle (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1), startTimeSeconds, endTimeSeconds, i, verticalZoomFactor); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnail.cpp ***/ /*** Start of inlined file: juce_AudioThumbnailCache.cpp ***/ BEGIN_JUCE_NAMESPACE struct ThumbnailCacheEntry { int64 hash; uint32 lastUsed; MemoryBlock data; JUCE_LEAK_DETECTOR (ThumbnailCacheEntry); }; AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbsToStore_) : TimeSliceThread ("thumb cache"), maxNumThumbsToStore (maxNumThumbsToStore_) { startThread (2); } AudioThumbnailCache::~AudioThumbnailCache() { } ThumbnailCacheEntry* AudioThumbnailCache::findThumbFor (const int64 hash) const { for (int i = thumbs.size(); --i >= 0;) if (thumbs.getUnchecked(i)->hash == hash) return thumbs.getUnchecked(i); return nullptr; } bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) { ThumbnailCacheEntry* te = findThumbFor (hashCode); if (te != nullptr) { te->lastUsed = Time::getMillisecondCounter(); MemoryInputStream in (te->data, false); thumb.loadFrom (in); return true; } return false; } void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, const int64 hashCode) { ThumbnailCacheEntry* te = findThumbFor (hashCode); if (te == nullptr) { te = new ThumbnailCacheEntry(); te->hash = hashCode; if (thumbs.size() < maxNumThumbsToStore) { thumbs.add (te); } else { int oldest = 0; uint32 oldestTime = Time::getMillisecondCounter() + 1; for (int i = thumbs.size(); --i >= 0;) { if (thumbs.getUnchecked(i)->lastUsed < oldestTime) { oldest = i; oldestTime = thumbs.getUnchecked(i)->lastUsed; } } thumbs.set (oldest, te); } } te->lastUsed = Time::getMillisecondCounter(); MemoryOutputStream out (te->data, false); thumb.saveTo (out); } void AudioThumbnailCache::clear() { thumbs.clear(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnailCache.cpp ***/ /*** Start of inlined file: juce_QuickTimeAudioFormat.cpp ***/ #if JUCE_QUICKTIME && ! (JUCE_64BIT || JUCE_IOS) #if ! JUCE_WINDOWS #include #include #include #include #include #else #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4100) #endif /* If you've got an include error here, you probably need to install the QuickTime SDK and add its header directory to your include path. Alternatively, if you don't need any QuickTime services, just turn off the JUCE_QUICKTIME flag in juce_Config.h */ #include #include #include #include #include #if JUCE_MSVC #pragma warning (pop) #endif #endif BEGIN_JUCE_NAMESPACE bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle); static const char* const quickTimeFormatName = "QuickTime file"; static const char* const quickTimeExtensions[] = { ".mov", ".mp3", ".mp4", ".m4a", 0 }; class QTAudioReader : public AudioFormatReader { public: QTAudioReader (InputStream* const input_, const int trackNum_) : AudioFormatReader (input_, TRANS (quickTimeFormatName)), ok (false), movie (0), trackNum (trackNum_), lastSampleRead (0), lastThreadId (0), extractor (0), dataHandle (0) { JUCE_AUTORELEASEPOOL bufferList.calloc (256, 1); #if JUCE_WINDOWS if (InitializeQTML (0) != noErr) return; #endif if (EnterMovies() != noErr) return; bool opened = juce_OpenQuickTimeMovieFromStream (input_, movie, dataHandle); if (! opened) return; { const int numTracks = GetMovieTrackCount (movie); int trackCount = 0; for (int i = 1; i <= numTracks; ++i) { track = GetMovieIndTrack (movie, i); media = GetTrackMedia (track); OSType mediaType; GetMediaHandlerDescription (media, &mediaType, 0, 0); if (mediaType == SoundMediaType && trackCount++ == trackNum_) { ok = true; break; } } } if (! ok) return; ok = false; lengthInSamples = GetMediaDecodeDuration (media); usesFloatingPointData = false; samplesPerFrame = (int) (GetMediaDecodeDuration (media) / GetMediaSampleCount (media)); trackUnitsPerFrame = GetMovieTimeScale (movie) * samplesPerFrame / GetMediaTimeScale (media); OSStatus err = MovieAudioExtractionBegin (movie, 0, &extractor); unsigned long output_layout_size; err = MovieAudioExtractionGetPropertyInfo (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, 0, &output_layout_size, 0); if (err != noErr) return; HeapBlock qt_audio_channel_layout; qt_audio_channel_layout.calloc (output_layout_size, 1); err = MovieAudioExtractionGetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, output_layout_size, qt_audio_channel_layout, 0); qt_audio_channel_layout[0].mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, output_layout_size, qt_audio_channel_layout); err = MovieAudioExtractionGetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof (inputStreamDesc), &inputStreamDesc, 0); if (err != noErr) return; inputStreamDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; inputStreamDesc.mBitsPerChannel = sizeof (SInt16) * 8; inputStreamDesc.mChannelsPerFrame = jmin ((UInt32) 2, inputStreamDesc.mChannelsPerFrame); inputStreamDesc.mBytesPerFrame = sizeof (SInt16) * inputStreamDesc.mChannelsPerFrame; inputStreamDesc.mBytesPerPacket = inputStreamDesc.mBytesPerFrame; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof (inputStreamDesc), &inputStreamDesc); if (err != noErr) return; Boolean allChannelsDiscrete = false; err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Movie, kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, sizeof (allChannelsDiscrete), &allChannelsDiscrete); if (err != noErr) return; bufferList->mNumberBuffers = 1; bufferList->mBuffers[0].mNumberChannels = inputStreamDesc.mChannelsPerFrame; bufferList->mBuffers[0].mDataByteSize = jmax ((UInt32) 4096, (UInt32) (samplesPerFrame * inputStreamDesc.mBytesPerFrame) + 16); dataBuffer.malloc (bufferList->mBuffers[0].mDataByteSize); bufferList->mBuffers[0].mData = dataBuffer; sampleRate = inputStreamDesc.mSampleRate; bitsPerSample = 16; numChannels = inputStreamDesc.mChannelsPerFrame; detachThread(); ok = true; } ~QTAudioReader() { JUCE_AUTORELEASEPOOL checkThreadIsAttached(); if (dataHandle != nullptr) DisposeHandle (dataHandle); if (extractor != nullptr) { MovieAudioExtractionEnd (extractor); extractor = nullptr; } DisposeMovie (movie); #if JUCE_MAC ExitMoviesOnThread (); #endif } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { JUCE_AUTORELEASEPOOL checkThreadIsAttached(); bool ok = true; while (numSamples > 0) { if (lastSampleRead != startSampleInFile) { TimeRecord time; time.scale = (TimeScale) inputStreamDesc.mSampleRate; time.base = 0; time.value.hi = 0; time.value.lo = (UInt32) startSampleInFile; OSStatus err = MovieAudioExtractionSetProperty (extractor, kQTPropertyClass_MovieAudioExtraction_Movie, kQTMovieAudioExtractionMoviePropertyID_CurrentTime, sizeof (time), &time); if (err != noErr) { ok = false; break; } } int framesToDo = jmin (numSamples, (int) (bufferList->mBuffers[0].mDataByteSize / inputStreamDesc.mBytesPerFrame)); bufferList->mBuffers[0].mDataByteSize = inputStreamDesc.mBytesPerFrame * framesToDo; UInt32 outFlags = 0; UInt32 actualNumFrames = framesToDo; OSStatus err = MovieAudioExtractionFillBuffer (extractor, &actualNumFrames, bufferList, &outFlags); if (err != noErr) { ok = false; break; } lastSampleRead = startSampleInFile + actualNumFrames; const int samplesReceived = actualNumFrames; for (int j = numDestChannels; --j >= 0;) { if (destSamples[j] != nullptr) { const short* src = ((const short*) bufferList->mBuffers[0].mData) + j; for (int i = 0; i < samplesReceived; ++i) { destSamples[j][startOffsetInDestBuffer + i] = (*src << 16); src += numChannels; } } } startOffsetInDestBuffer += samplesReceived; startSampleInFile += samplesReceived; numSamples -= samplesReceived; if (((outFlags & kQTMovieAudioExtractionComplete) != 0 || samplesReceived == 0) && numSamples > 0) { for (int j = numDestChannels; --j >= 0;) if (destSamples[j] != nullptr) zeromem (destSamples[j] + startOffsetInDestBuffer, sizeof (int) * numSamples); break; } } detachThread(); return ok; } bool ok; private: Movie movie; Media media; Track track; const int trackNum; double trackUnitsPerFrame; int samplesPerFrame; int64 lastSampleRead; Thread::ThreadID lastThreadId; MovieAudioExtractionRef extractor; AudioStreamBasicDescription inputStreamDesc; HeapBlock bufferList; HeapBlock dataBuffer; Handle dataHandle; void checkThreadIsAttached() { #if JUCE_MAC if (Thread::getCurrentThreadId() != lastThreadId) EnterMoviesOnThread (0); AttachMovieToCurrentThread (movie); #endif } void detachThread() { #if JUCE_MAC DetachMovieFromCurrentThread (movie); #endif } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QTAudioReader); }; QuickTimeAudioFormat::QuickTimeAudioFormat() : AudioFormat (TRANS (quickTimeFormatName), StringArray (quickTimeExtensions)) { } QuickTimeAudioFormat::~QuickTimeAudioFormat() { } const Array QuickTimeAudioFormat::getPossibleSampleRates() { return Array(); } const Array QuickTimeAudioFormat::getPossibleBitDepths() { return Array(); } bool QuickTimeAudioFormat::canDoStereo() { return true; } bool QuickTimeAudioFormat::canDoMono() { return true; } AudioFormatReader* QuickTimeAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer r (new QTAudioReader (sourceStream, 0)); if (r->ok) return r.release(); if (! deleteStreamIfOpeningFails) r->input = 0; return nullptr; } AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/, unsigned int /*numberOfChannels*/, int /*bitsPerSample*/, const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/) { jassertfalse; // not yet implemented! return nullptr; } END_JUCE_NAMESPACE #endif /*** End of inlined file: juce_QuickTimeAudioFormat.cpp ***/ /*** Start of inlined file: juce_WavAudioFormat.cpp ***/ BEGIN_JUCE_NAMESPACE static const char* const wavFormatName = "WAV file"; static const char* const wavExtensions[] = { ".wav", ".bwf", 0 }; const char* const WavAudioFormat::bwavDescription = "bwav description"; const char* const WavAudioFormat::bwavOriginator = "bwav originator"; const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref"; const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date"; const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time"; const char* const WavAudioFormat::bwavTimeReference = "bwav time reference"; const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history"; StringPairArray WavAudioFormat::createBWAVMetadata (const String& description, const String& originator, const String& originatorRef, const Time& date, const int64 timeReferenceSamples, const String& codingHistory) { StringPairArray m; m.set (bwavDescription, description); m.set (bwavOriginator, originator); m.set (bwavOriginatorRef, originatorRef); m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d")); m.set (bwavOriginationTime, date.formatted ("%H:%M:%S")); m.set (bwavTimeReference, String (timeReferenceSamples)); m.set (bwavCodingHistory, codingHistory); return m; } namespace WavFileHelpers { inline int chunkName (const char* const name) noexcept { return (int) ByteOrder::littleEndianInt (name); } #if JUCE_MSVC #pragma pack (push, 1) #define PACKED #elif JUCE_GCC #define PACKED __attribute__((packed)) #else #define PACKED #endif struct BWAVChunk { char description [256]; char originator [32]; char originatorRef [32]; char originationDate [10]; char originationTime [8]; uint32 timeRefLow; uint32 timeRefHigh; uint16 version; uint8 umid[64]; uint8 reserved[190]; char codingHistory[1]; void copyTo (StringPairArray& values) const { values.set (WavAudioFormat::bwavDescription, String::fromUTF8 (description, 256)); values.set (WavAudioFormat::bwavOriginator, String::fromUTF8 (originator, 32)); values.set (WavAudioFormat::bwavOriginatorRef, String::fromUTF8 (originatorRef, 32)); values.set (WavAudioFormat::bwavOriginationDate, String::fromUTF8 (originationDate, 10)); values.set (WavAudioFormat::bwavOriginationTime, String::fromUTF8 (originationTime, 8)); const uint32 timeLow = ByteOrder::swapIfBigEndian (timeRefLow); const uint32 timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh); const int64 time = (((int64)timeHigh) << 32) + timeLow; values.set (WavAudioFormat::bwavTimeReference, String (time)); values.set (WavAudioFormat::bwavCodingHistory, String::fromUTF8 (codingHistory)); } static MemoryBlock createFrom (const StringPairArray& values) { const size_t sizeNeeded = sizeof (BWAVChunk) + values [WavAudioFormat::bwavCodingHistory].getNumBytesAsUTF8(); MemoryBlock data ((sizeNeeded + 3) & ~3); data.fillWith (0); BWAVChunk* b = (BWAVChunk*) data.getData(); // Allow these calls to overwrite an extra byte at the end, which is fine as long // as they get called in the right order.. values [WavAudioFormat::bwavDescription].copyToUTF8 (b->description, 257); values [WavAudioFormat::bwavOriginator].copyToUTF8 (b->originator, 33); values [WavAudioFormat::bwavOriginatorRef].copyToUTF8 (b->originatorRef, 33); values [WavAudioFormat::bwavOriginationDate].copyToUTF8 (b->originationDate, 11); values [WavAudioFormat::bwavOriginationTime].copyToUTF8 (b->originationTime, 9); const int64 time = values [WavAudioFormat::bwavTimeReference].getLargeIntValue(); b->timeRefLow = ByteOrder::swapIfBigEndian ((uint32) (time & 0xffffffff)); b->timeRefHigh = ByteOrder::swapIfBigEndian ((uint32) (time >> 32)); values [WavAudioFormat::bwavCodingHistory].copyToUTF8 (b->codingHistory, 0x7fffffff); if (b->description[0] != 0 || b->originator[0] != 0 || b->originationDate[0] != 0 || b->originationTime[0] != 0 || b->codingHistory[0] != 0 || time != 0) { return data; } return MemoryBlock(); } } PACKED; struct SMPLChunk { struct SampleLoop { uint32 identifier; uint32 type; // these are different in AIFF and WAV uint32 start; uint32 end; uint32 fraction; uint32 playCount; } PACKED; uint32 manufacturer; uint32 product; uint32 samplePeriod; uint32 midiUnityNote; uint32 midiPitchFraction; uint32 smpteFormat; uint32 smpteOffset; uint32 numSampleLoops; uint32 samplerData; SampleLoop loops[1]; void copyTo (StringPairArray& values, const int totalSize) const { values.set ("Manufacturer", String (ByteOrder::swapIfBigEndian (manufacturer))); values.set ("Product", String (ByteOrder::swapIfBigEndian (product))); values.set ("SamplePeriod", String (ByteOrder::swapIfBigEndian (samplePeriod))); values.set ("MidiUnityNote", String (ByteOrder::swapIfBigEndian (midiUnityNote))); values.set ("MidiPitchFraction", String (ByteOrder::swapIfBigEndian (midiPitchFraction))); values.set ("SmpteFormat", String (ByteOrder::swapIfBigEndian (smpteFormat))); values.set ("SmpteOffset", String (ByteOrder::swapIfBigEndian (smpteOffset))); values.set ("NumSampleLoops", String (ByteOrder::swapIfBigEndian (numSampleLoops))); values.set ("SamplerData", String (ByteOrder::swapIfBigEndian (samplerData))); for (uint32 i = 0; i < numSampleLoops; ++i) { if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize) break; const String prefix ("Loop" + String(i)); values.set (prefix + "Identifier", String (ByteOrder::swapIfBigEndian (loops[i].identifier))); values.set (prefix + "Type", String (ByteOrder::swapIfBigEndian (loops[i].type))); values.set (prefix + "Start", String (ByteOrder::swapIfBigEndian (loops[i].start))); values.set (prefix + "End", String (ByteOrder::swapIfBigEndian (loops[i].end))); values.set (prefix + "Fraction", String (ByteOrder::swapIfBigEndian (loops[i].fraction))); values.set (prefix + "PlayCount", String (ByteOrder::swapIfBigEndian (loops[i].playCount))); } } static MemoryBlock createFrom (const StringPairArray& values) { const int numLoops = jmin (64, values.getValue ("NumSampleLoops", "0").getIntValue()); if (numLoops <= 0) return MemoryBlock(); const size_t sizeNeeded = sizeof (SMPLChunk) + (numLoops - 1) * sizeof (SampleLoop); MemoryBlock data ((sizeNeeded + 3) & ~3); data.fillWith (0); SMPLChunk* const s = static_cast (data.getData()); s->manufacturer = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Manufacturer", "0").getIntValue()); s->product = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Product", "0").getIntValue()); s->samplePeriod = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SamplePeriod", "0").getIntValue()); s->midiUnityNote = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("MidiUnityNote", "60").getIntValue()); s->midiPitchFraction = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("MidiPitchFraction", "0").getIntValue()); s->smpteFormat = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SmpteFormat", "0").getIntValue()); s->smpteOffset = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SmpteOffset", "0").getIntValue()); s->numSampleLoops = ByteOrder::swapIfBigEndian ((uint32) numLoops); s->samplerData = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SamplerData", "0").getIntValue()); for (int i = 0; i < numLoops; ++i) { const String prefix ("Loop" + String(i)); s->loops[i].identifier = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Identifier", "0").getIntValue()); s->loops[i].type = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Type", "0").getIntValue()); s->loops[i].start = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Start", "0").getIntValue()); s->loops[i].end = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "End", "0").getIntValue()); s->loops[i].fraction = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Fraction", "0").getIntValue()); s->loops[i].playCount = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "PlayCount", "0").getIntValue()); } return data; } } PACKED; struct InstChunk { int8 baseNote; int8 detune; int8 gain; int8 lowNote; int8 highNote; int8 lowVelocity; int8 highVelocity; void copyTo (StringPairArray& values) const { values.set ("MidiUnityNote", String (baseNote)); values.set ("Detune", String (detune)); values.set ("Gain", String (gain)); values.set ("LowNote", String (lowNote)); values.set ("HighNote", String (highNote)); values.set ("LowVelocity", String (lowVelocity)); values.set ("HighVelocity", String (highVelocity)); } static MemoryBlock createFrom (const StringPairArray& values) { const StringArray& keys = values.getAllKeys(); if (! (keys.contains ("LowNote", true) && keys.contains ("HighNote", true))) return MemoryBlock(); MemoryBlock data (8); data.fillWith (0); InstChunk* const inst = static_cast (data.getData()); inst->baseNote = (int8) values.getValue ("MidiUnityNote", "60").getIntValue(); inst->detune = (int8) values.getValue ("Detune", "0").getIntValue(); inst->gain = (int8) values.getValue ("Gain", "0").getIntValue(); inst->lowNote = (int8) values.getValue ("LowNote", "0").getIntValue(); inst->highNote = (int8) values.getValue ("HighNote", "127").getIntValue(); inst->lowVelocity = (int8) values.getValue ("LowVelocity", "1").getIntValue(); inst->highVelocity = (int8) values.getValue ("HighVelocity", "127").getIntValue(); return data; } } PACKED; struct CueChunk { struct Cue { uint32 identifier; uint32 order; uint32 chunkID; uint32 chunkStart; uint32 blockStart; uint32 offset; } PACKED; uint32 numCues; Cue cues[1]; void copyTo (StringPairArray& values, const int totalSize) const { values.set ("NumCuePoints", String (ByteOrder::swapIfBigEndian (numCues))); for (uint32 i = 0; i < numCues; ++i) { if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize) break; const String prefix ("Cue" + String(i)); values.set (prefix + "Identifier", String (ByteOrder::swapIfBigEndian (cues[i].identifier))); values.set (prefix + "Order", String (ByteOrder::swapIfBigEndian (cues[i].order))); values.set (prefix + "ChunkID", String (ByteOrder::swapIfBigEndian (cues[i].chunkID))); values.set (prefix + "ChunkStart", String (ByteOrder::swapIfBigEndian (cues[i].chunkStart))); values.set (prefix + "BlockStart", String (ByteOrder::swapIfBigEndian (cues[i].blockStart))); values.set (prefix + "Offset", String (ByteOrder::swapIfBigEndian (cues[i].offset))); } } static void create (MemoryBlock& data, const StringPairArray& values) { const int numCues = values.getValue ("NumCuePoints", "0").getIntValue(); if (numCues > 0) { const size_t sizeNeeded = sizeof (CueChunk) + (numCues - 1) * sizeof (Cue); data.setSize ((sizeNeeded + 3) & ~3, true); CueChunk* const c = static_cast (data.getData()); c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues); const String dataChunkID (chunkName ("data")); int nextOrder = 0; #if JUCE_DEBUG Array identifiers; #endif for (int i = 0; i < numCues; ++i) { const String prefix ("Cue" + String (i)); uint32 identifier = values.getValue (prefix + "Identifier", "0").getIntValue(); #if JUCE_DEBUG jassert (! identifiers.contains (identifier)); identifiers.add (identifier); #endif c->cues[i].identifier = ByteOrder::swapIfBigEndian ((uint32) identifier); const int order = values.getValue (prefix + "Order", String (nextOrder)).getIntValue(); nextOrder = jmax (nextOrder, order) + 1; c->cues[i].order = ByteOrder::swapIfBigEndian ((uint32) order); c->cues[i].chunkID = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkID", dataChunkID).getIntValue()); c->cues[i].chunkStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkStart", "0").getIntValue()); c->cues[i].blockStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "BlockStart", "0").getIntValue()); c->cues[i].offset = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Offset", "0").getIntValue()); } } } } PACKED; namespace ListChunk { void appendLabelOrNoteChunk (const StringPairArray& values, const String& prefix, const int chunkType, MemoryOutputStream& out) { const String label (values.getValue (prefix + "Text", prefix)); const int labelLength = label.getNumBytesAsUTF8() + 1; const int chunkLength = 4 + labelLength + (labelLength & 1); out.writeInt (chunkType); out.writeInt (chunkLength); out.writeInt (values.getValue (prefix + "Identifier", "0").getIntValue()); out.write (label.toUTF8(), labelLength); if ((out.getDataSize() & 1) != 0) out.writeByte (0); } void appendExtraChunk (const StringPairArray& values, const String& prefix, MemoryOutputStream& out) { const String text (values.getValue (prefix + "Text", prefix)); const int textLength = text.getNumBytesAsUTF8() + 1; // include null terminator uint32 chunkLength = textLength + 20 + (textLength & 1); out.writeInt (chunkName ("ltxt")); out.writeInt (chunkLength); out.writeInt (values.getValue (prefix + "Identifier", "0").getIntValue()); out.writeInt (values.getValue (prefix + "SampleLength", "0").getIntValue()); out.writeInt (values.getValue (prefix + "Purpose", "0").getIntValue()); out.writeShort ((short) values.getValue (prefix + "Country", "0").getIntValue()); out.writeShort ((short) values.getValue (prefix + "Language", "0").getIntValue()); out.writeShort ((short) values.getValue (prefix + "Dialect", "0").getIntValue()); out.writeShort ((short) values.getValue (prefix + "CodePage", "0").getIntValue()); out.write (text.toUTF8(), textLength); if ((out.getDataSize() & 1) != 0) out.writeByte (0); } void create (MemoryBlock& block, const StringPairArray& values) { const int numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue(); const int numCueNotes = values.getValue ("NumCueNotes", "0").getIntValue(); const int numCueRegions = values.getValue ("NumCueRegions", "0").getIntValue(); if (numCueLabels > 0 || numCueNotes > 0 || numCueRegions > 0) { MemoryOutputStream out (block, false); int i; for (i = 0; i < numCueLabels; ++i) appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out); for (i = 0; i < numCueNotes; ++i) appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out); for (i = 0; i < numCueRegions; ++i) appendExtraChunk (values, "CueRegion" + String (i), out); } } } struct ExtensibleWavSubFormat { uint32 data1; uint16 data2; uint16 data3; uint8 data4[8]; } PACKED; struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise { uint32 riffSizeLow; // low 4 byte size of RF64 block uint32 riffSizeHigh; // high 4 byte size of RF64 block uint32 dataSizeLow; // low 4 byte size of data chunk uint32 dataSizeHigh; // high 4 byte size of data chunk uint32 sampleCountLow; // low 4 byte sample count of fact chunk uint32 sampleCountHigh; // high 4 byte sample count of fact chunk uint32 tableLength; // number of valid entries in array 'table' } PACKED; #if JUCE_MSVC #pragma pack (pop) #endif #undef PACKED } class WavAudioFormatReader : public AudioFormatReader { public: WavAudioFormatReader (InputStream* const in) : AudioFormatReader (in, TRANS (wavFormatName)), bwavChunkStart (0), bwavSize (0), dataLength (0), isRF64 (false) { using namespace WavFileHelpers; uint64 len = 0; int64 end = 0; bool hasGotType = false; bool hasGotData = false; int cueNoteIndex = 0; int cueLabelIndex = 0; int cueRegionIndex = 0; const int firstChunkType = input->readInt(); if (firstChunkType == chunkName ("RF64")) { input->skipNextBytes (4); // size is -1 for RF64 isRF64 = true; } else if (firstChunkType == chunkName ("RIFF")) { len = (uint64) input->readInt(); end = input->getPosition() + len; } else { return; } const int64 startOfRIFFChunk = input->getPosition(); if (input->readInt() == chunkName ("WAVE")) { if (isRF64 && input->readInt() == chunkName ("ds64")) { uint32 length = (uint32) input->readInt(); if (length < 28) { return; } else { const int64 chunkEnd = input->getPosition() + length + (length & 1); len = input->readInt64(); end = startOfRIFFChunk + len; dataLength = input->readInt64(); input->setPosition (chunkEnd); } } while (input->getPosition() < end && ! input->isExhausted()) { const int chunkType = input->readInt(); uint32 length = (uint32) input->readInt(); const int64 chunkEnd = input->getPosition() + length + (length & 1); if (chunkType == chunkName ("fmt ")) { // read the format chunk const unsigned short format = input->readShort(); const short numChans = input->readShort(); sampleRate = input->readInt(); const int bytesPerSec = input->readInt(); numChannels = numChans; bytesPerFrame = bytesPerSec / (int)sampleRate; bitsPerSample = 8 * bytesPerFrame / numChans; if (format == 3) { usesFloatingPointData = true; } else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) { if (length < 40) // too short { bytesPerFrame = 0; } else { input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask ExtensibleWavSubFormat subFormat; subFormat.data1 = input->readInt(); subFormat.data2 = input->readShort(); subFormat.data3 = input->readShort(); input->read (subFormat.data4, sizeof (subFormat.data4)); const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) { const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) bytesPerFrame = 0; } } } else if (format != 1) { bytesPerFrame = 0; } hasGotType = true; } else if (chunkType == chunkName ("data")) { if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk dataLength = length; dataChunkStart = input->getPosition(); lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; hasGotData = true; } else if (chunkType == chunkName ("bext")) { bwavChunkStart = input->getPosition(); bwavSize = length; HeapBlock bwav; bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); input->read (bwav, length); bwav->copyTo (metadataValues); } else if (chunkType == chunkName ("smpl")) { HeapBlock smpl; smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); input->read (smpl, length); smpl->copyTo (metadataValues, length); } else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which... { HeapBlock inst; inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); input->read (inst, length); inst->copyTo (metadataValues); } else if (chunkType == chunkName ("cue ")) { HeapBlock cue; cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1); input->read (cue, length); cue->copyTo (metadataValues, length); } else if (chunkType == chunkName ("LIST")) { if (input->readInt() == chunkName ("adtl")) { while (input->getPosition() < chunkEnd) { const int adtlChunkType = input->readInt(); const uint32 adtlLength = (uint32) input->readInt(); const int64 adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1)); if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note")) { String prefix; if (adtlChunkType == chunkName ("labl")) prefix << "CueLabel" << cueLabelIndex++; else if (adtlChunkType == chunkName ("note")) prefix << "CueNote" << cueNoteIndex++; const uint32 identifier = (uint32) input->readInt(); const uint32 stringLength = adtlLength - 4; MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength); const String text (String::fromUTF8 (static_cast (textBlock.getData()), textBlock.getSize())); metadataValues.set (prefix + "Identifier", String (identifier)); metadataValues.set (prefix + "Text", text); } else if (adtlChunkType == chunkName ("ltxt")) { const String prefix ("CueRegion" + String (cueRegionIndex++)); const uint32 identifier = (uint32) input->readInt(); const uint32 sampleLength = (uint32) input->readInt(); const uint32 purpose = (uint32) input->readInt(); const uint16 country = (uint16) input->readInt(); const uint16 language = (uint16) input->readInt(); const uint16 dialect = (uint16) input->readInt(); const uint16 codePage = (uint16) input->readInt(); const uint32 stringLength = adtlLength - 20; MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength); const String text = String::fromUTF8 ((const char*)textBlock.getData(), textBlock.getSize()); metadataValues.set (prefix + "Identifier", String (identifier)); metadataValues.set (prefix + "SampleLength", String (sampleLength)); metadataValues.set (prefix + "Purpose", String (purpose)); metadataValues.set (prefix + "Country", String (country)); metadataValues.set (prefix + "Language", String (language)); metadataValues.set (prefix + "Dialect", String (dialect)); metadataValues.set (prefix + "CodePage", String (codePage)); metadataValues.set (prefix + "Text", text); } input->setPosition (adtlChunkEnd); } } } else if (chunkEnd <= input->getPosition()) { break; } input->setPosition (chunkEnd); } } if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex)); if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex)); if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex)); if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV"); } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { jassert (destSamples != nullptr); const int64 samplesAvailable = lengthInSamples - startSampleInFile; if (samplesAvailable < numSamples) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != nullptr) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * numSamples); numSamples = (int) samplesAvailable; } if (numSamples <= 0) return true; input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); while (numSamples > 0) { const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) char tempBuffer [tempBufSize]; const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); if (bytesRead < numThisTime * bytesPerFrame) { jassert (bytesRead >= 0); zeromem (tempBuffer + bytesRead, numThisTime * bytesPerFrame - bytesRead); } switch (bitsPerSample) { case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; case 32: if (usesFloatingPointData) ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); else ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, numChannels, numThisTime); break; default: jassertfalse; break; } startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; } return true; } int64 bwavChunkStart, bwavSize; private: ScopedPointer converter; int bytesPerFrame; int64 dataChunkStart, dataLength; bool isRF64; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader); }; class WavAudioFormatWriter : public AudioFormatWriter { public: WavAudioFormatWriter (OutputStream* const out, const double sampleRate_, const unsigned int numChannels_, const int bits, const StringPairArray& metadataValues) : AudioFormatWriter (out, TRANS (wavFormatName), sampleRate_, numChannels_, bits), lengthInSamples (0), bytesWritten (0), writeFailed (false) { using namespace WavFileHelpers; if (metadataValues.size() > 0) { // The meta data should have been santised for the WAV format. // If it was originally sourced from an AIFF file the MetaDataSource // key should be removed (or set to "WAV") once this has been done jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF"); bwavChunk = BWAVChunk::createFrom (metadataValues); smplChunk = SMPLChunk::createFrom (metadataValues); instChunk = InstChunk::createFrom (metadataValues); CueChunk ::create (cueChunk, metadataValues); ListChunk::create (listChunk, metadataValues); } headerPosition = out->getPosition(); writeHeader(); } ~WavAudioFormatWriter() { if ((bytesWritten & 1) != 0) // pad to an even length { ++bytesWritten; output->writeByte (0); } writeHeader(); } bool write (const int** data, int numSamples) { jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! if (writeFailed) return false; const int bytes = numChannels * numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); switch (bitsPerSample) { case 8: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 16: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 24: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; case 32: WriteHelper::write (tempBlock.getData(), numChannels, data, numSamples); break; default: jassertfalse; break; } if (! output->write (tempBlock.getData(), bytes)) { // failed to write to disk, so let's try writing the header. // If it's just run out of disk space, then if it does manage // to write the header, we'll still have a useable file.. writeHeader(); writeFailed = true; return false; } else { bytesWritten += bytes; lengthInSamples += numSamples; return true; } } private: ScopedPointer converter; MemoryBlock tempBlock, bwavChunk, smplChunk, instChunk, cueChunk, listChunk; uint64 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; static int getChannelMask (const int numChannels) noexcept { switch (numChannels) { case 1: return 0; case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT default: break; } return 0; } void writeHeader() { using namespace WavFileHelpers; const bool seekedOk = output->setPosition (headerPosition); (void) seekedOk; // if this fails, you've given it an output stream that can't seek! It needs // to be able to seek back to write the header jassert (seekedOk); const int bytesPerFrame = numChannels * bitsPerSample / 8; int64 audioDataSize = bytesPerFrame * lengthInSamples; const bool isRF64 = (bytesWritten >= literal64bit (0x100000000)); const bool isWaveFmtEx = isRF64 || (numChannels > 2); int64 riffChunkSize = 4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */ + 8 + audioDataSize + (audioDataSize & 1) + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) + (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0) + (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0) + (listChunk.getSize() > 0 ? (12 + listChunk.getSize()) : 0) + (8 + 28); // (ds64 chunk) riffChunkSize += (riffChunkSize & 0x1); output->writeInt (chunkName (isRF64 ? "RF64" : "RIFF")); output->writeInt (isRF64 ? -1 : (int) riffChunkSize); output->writeInt (chunkName ("WAVE")); if (! isRF64) { output->writeInt (chunkName ("JUNK")); output->writeInt (28 + (isWaveFmtEx? 0 : 24)); output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24)); } else { // write ds64 chunk output->writeInt (chunkName ("ds64")); output->writeInt (28); // chunk size for uncompressed data (no table) output->writeInt64 (riffChunkSize); output->writeInt64 (audioDataSize); output->writeRepeatedByte (0, 12); } output->writeInt (chunkName ("fmt ")); if (isWaveFmtEx) { output->writeInt (40); // chunk size output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE } else { output->writeInt (16); // chunk size output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/ : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/); } output->writeShort ((short) numChannels); output->writeInt ((int) sampleRate); output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec output->writeShort ((short) bytesPerFrame); // nBlockAlign output->writeShort ((short) bitsPerSample); // wBitsPerSample if (isWaveFmtEx) { output->writeShort (22); // cbSize (size of the extension) output->writeShort ((short) bitsPerSample); // wValidBitsPerSample output->writeInt (getChannelMask (numChannels)); const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat; output->writeInt ((int) subFormat.data1); output->writeShort ((short) subFormat.data2); output->writeShort ((short) subFormat.data3); output->write (subFormat.data4, sizeof (subFormat.data4)); } if (bwavChunk.getSize() > 0) { output->writeInt (chunkName ("bext")); output->writeInt ((int) bwavChunk.getSize()); output->write (bwavChunk.getData(), (int) bwavChunk.getSize()); } if (smplChunk.getSize() > 0) { output->writeInt (chunkName ("smpl")); output->writeInt ((int) smplChunk.getSize()); output->write (smplChunk.getData(), (int) smplChunk.getSize()); } if (instChunk.getSize() > 0) { output->writeInt (chunkName ("inst")); output->writeInt (7); output->write (instChunk.getData(), (int) instChunk.getSize()); } if (cueChunk.getSize() > 0) { output->writeInt (chunkName ("cue ")); output->writeInt ((int) cueChunk.getSize()); output->write (cueChunk.getData(), (int) cueChunk.getSize()); } if (listChunk.getSize() > 0) { output->writeInt (chunkName ("LIST")); output->writeInt ((int) listChunk.getSize() + 4); output->writeInt (chunkName ("adtl")); output->write (listChunk.getData(), (int) listChunk.getSize()); } output->writeInt (chunkName ("data")); output->writeInt (isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); usesFloatingPointData = (bitsPerSample == 32); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter); }; WavAudioFormat::WavAudioFormat() : AudioFormat (TRANS (wavFormatName), StringArray (wavExtensions)) { } WavAudioFormat::~WavAudioFormat() { } const Array WavAudioFormat::getPossibleSampleRates() { const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; return Array (rates); } const Array WavAudioFormat::getPossibleBitDepths() { const int depths[] = { 8, 16, 24, 32, 0 }; return Array (depths); } bool WavAudioFormat::canDoStereo() { return true; } bool WavAudioFormat::canDoMono() { return true; } AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) { ScopedPointer r (new WavAudioFormatReader (sourceStream)); if (r->sampleRate > 0) return r.release(); if (! deleteStreamIfOpeningFails) r->input = nullptr; return nullptr; } AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numChannels, int bitsPerSample, const StringPairArray& metadataValues, int /*qualityOptionIndex*/) { if (getPossibleBitDepths().contains (bitsPerSample)) return new WavAudioFormatWriter (out, sampleRate, numChannels, bitsPerSample, metadataValues); return nullptr; } namespace WavFileHelpers { bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) { TemporaryFile tempFile (file); WavAudioFormat wav; ScopedPointer reader (wav.createReaderFor (file.createInputStream(), true)); if (reader != nullptr) { ScopedPointer outStream (tempFile.getFile().createOutputStream()); if (outStream != nullptr) { ScopedPointer writer (wav.createWriterFor (outStream, reader->sampleRate, reader->numChannels, reader->bitsPerSample, metadata, 0)); if (writer != nullptr) { outStream.release(); bool ok = writer->writeFromAudioReader (*reader, 0, -1); writer = nullptr; reader = nullptr; return ok && tempFile.overwriteTargetFileWithTemporary(); } } } return false; } } bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata) { using namespace WavFileHelpers; ScopedPointer reader (static_cast (createReaderFor (wavFile.createInputStream(), true))); if (reader != nullptr) { const int64 bwavPos = reader->bwavChunkStart; const int64 bwavSize = reader->bwavSize; reader = nullptr; if (bwavSize > 0) { MemoryBlock chunk = BWAVChunk::createFrom (newMetadata); if (chunk.getSize() <= (size_t) bwavSize) { // the new one will fit in the space available, so write it directly.. const int64 oldSize = wavFile.getSize(); { ScopedPointer out (wavFile.createOutputStream()); out->setPosition (bwavPos); out->write (chunk.getData(), (int) chunk.getSize()); out->setPosition (oldSize); } jassert (wavFile.getSize() == oldSize); return true; } } } return slowCopyWavFileWithNewMetadata (wavFile, newMetadata); } END_JUCE_NAMESPACE /*** End of inlined file: juce_WavAudioFormat.cpp ***/ /*** Start of inlined file: juce_AudioCDReader.cpp ***/ #if JUCE_USE_CDREADER BEGIN_JUCE_NAMESPACE int AudioCDReader::getNumTracks() const { return trackStartSamples.size() - 1; } int AudioCDReader::getPositionOfTrackStart (int trackNum) const { return trackStartSamples [trackNum]; } const Array& AudioCDReader::getTrackOffsets() const { return trackStartSamples; } int AudioCDReader::getCDDBId() { int checksum = 0; const int numTracks = getNumTracks(); for (int i = 0; i < numTracks; ++i) for (int offset = (trackStartSamples.getUnchecked(i) + 88200) / 44100; offset > 0; offset /= 10) checksum += offset % 10; const int length = (trackStartSamples.getLast() - trackStartSamples.getFirst()) / 44100; // CCLLLLTT: checksum, length, tracks return ((checksum & 0xff) << 24) | (length << 8) | numTracks; } END_JUCE_NAMESPACE #endif /*** End of inlined file: juce_AudioCDReader.cpp ***/ /*** Start of inlined file: juce_AudioFormatReaderSource.cpp ***/ BEGIN_JUCE_NAMESPACE AudioFormatReaderSource::AudioFormatReaderSource (AudioFormatReader* const reader_, const bool deleteReaderWhenThisIsDeleted) : reader (reader_, deleteReaderWhenThisIsDeleted), nextPlayPos (0), looping (false) { jassert (reader != nullptr); } AudioFormatReaderSource::~AudioFormatReaderSource() {} int64 AudioFormatReaderSource::getTotalLength() const { return reader->lengthInSamples; } void AudioFormatReaderSource::setNextReadPosition (int64 newPosition) { nextPlayPos = newPosition; } void AudioFormatReaderSource::setLooping (bool shouldLoop) { looping = shouldLoop; } int64 AudioFormatReaderSource::getNextReadPosition() const { return looping ? nextPlayPos % reader->lengthInSamples : nextPlayPos; } void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) {} void AudioFormatReaderSource::releaseResources() {} void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { if (info.numSamples > 0) { const int64 start = nextPlayPos; if (looping) { const int newStart = (int) (start % (int) reader->lengthInSamples); const int newEnd = (int) ((start + info.numSamples) % (int) reader->lengthInSamples); if (newEnd > newStart) { info.buffer->readFromAudioReader (reader, info.startSample, newEnd - newStart, newStart, true, true); } else { const int endSamps = (int) reader->lengthInSamples - newStart; info.buffer->readFromAudioReader (reader, info.startSample, endSamps, newStart, true, true); info.buffer->readFromAudioReader (reader, info.startSample + endSamps, newEnd, 0, true, true); } nextPlayPos = newEnd; } else { info.buffer->readFromAudioReader (reader, info.startSample, info.numSamples, start, true, true); nextPlayPos += info.numSamples; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatReaderSource.cpp ***/ /*** Start of inlined file: juce_AudioSourcePlayer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSourcePlayer::AudioSourcePlayer() : source (nullptr), sampleRate (0), bufferSize (0), tempBuffer (2, 8), lastGain (1.0f), gain (1.0f) { } AudioSourcePlayer::~AudioSourcePlayer() { setSource (nullptr); } void AudioSourcePlayer::setSource (AudioSource* newSource) { if (source != newSource) { AudioSource* const oldSource = source; if (newSource != nullptr && bufferSize > 0 && sampleRate > 0) newSource->prepareToPlay (bufferSize, sampleRate); { const ScopedLock sl (readLock); source = newSource; } if (oldSource != nullptr) oldSource->releaseResources(); } } void AudioSourcePlayer::setGain (const float newGain) noexcept { gain = newGain; } void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, int totalNumInputChannels, float** outputChannelData, int totalNumOutputChannels, int numSamples) { // these should have been prepared by audioDeviceAboutToStart()... jassert (sampleRate > 0 && bufferSize > 0); const ScopedLock sl (readLock); if (source != nullptr) { AudioSourceChannelInfo info; int i, numActiveChans = 0, numInputs = 0, numOutputs = 0; // messy stuff needed to compact the channels down into an array // of non-zero pointers.. for (i = 0; i < totalNumInputChannels; ++i) { if (inputChannelData[i] != nullptr) { inputChans [numInputs++] = inputChannelData[i]; if (numInputs >= numElementsInArray (inputChans)) break; } } for (i = 0; i < totalNumOutputChannels; ++i) { if (outputChannelData[i] != nullptr) { outputChans [numOutputs++] = outputChannelData[i]; if (numOutputs >= numElementsInArray (outputChans)) break; } } if (numInputs > numOutputs) { // if there aren't enough output channels for the number of // inputs, we need to create some temporary extra ones (can't // use the input data in case it gets written to) tempBuffer.setSize (numInputs - numOutputs, numSamples, false, false, true); for (i = 0; i < numOutputs; ++i) { channels[numActiveChans] = outputChans[i]; memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } for (i = numOutputs; i < numInputs; ++i) { channels[numActiveChans] = tempBuffer.getSampleData (i - numOutputs, 0); memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } } else { for (i = 0; i < numInputs; ++i) { channels[numActiveChans] = outputChans[i]; memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * numSamples); ++numActiveChans; } for (i = numInputs; i < numOutputs; ++i) { channels[numActiveChans] = outputChans[i]; zeromem (channels[numActiveChans], sizeof (float) * numSamples); ++numActiveChans; } } AudioSampleBuffer buffer (channels, numActiveChans, numSamples); info.buffer = &buffer; info.startSample = 0; info.numSamples = numSamples; source->getNextAudioBlock (info); for (i = info.buffer->getNumChannels(); --i >= 0;) info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); lastGain = gain; } else { for (int i = 0; i < totalNumOutputChannels; ++i) if (outputChannelData[i] != nullptr) zeromem (outputChannelData[i], sizeof (float) * numSamples); } } void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) { sampleRate = device->getCurrentSampleRate(); bufferSize = device->getCurrentBufferSizeSamples(); zeromem (channels, sizeof (channels)); if (source != nullptr) source->prepareToPlay (bufferSize, sampleRate); } void AudioSourcePlayer::audioDeviceStopped() { if (source != nullptr) source->releaseResources(); sampleRate = 0.0; bufferSize = 0; tempBuffer.setSize (2, 8); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSourcePlayer.cpp ***/ /*** Start of inlined file: juce_AudioTransportSource.cpp ***/ BEGIN_JUCE_NAMESPACE AudioTransportSource::AudioTransportSource() : source (nullptr), resamplerSource (nullptr), bufferingSource (nullptr), positionableSource (nullptr), masterSource (nullptr), gain (1.0f), lastGain (1.0f), playing (false), stopped (true), sampleRate (44100.0), sourceSampleRate (0.0), blockSize (128), readAheadBufferSize (0), isPrepared (false), inputStreamEOF (false) { } AudioTransportSource::~AudioTransportSource() { setSource (nullptr); releaseMasterResources(); } void AudioTransportSource::setSource (PositionableAudioSource* const newSource, int readAheadBufferSize_, double sourceSampleRateToCorrectFor, int maxNumChannels) { if (source == newSource) { if (source == nullptr) return; setSource (0, 0, 0); // deselect and reselect to avoid releasing resources wrongly } readAheadBufferSize = readAheadBufferSize_; sourceSampleRate = sourceSampleRateToCorrectFor; ResamplingAudioSource* newResamplerSource = nullptr; BufferingAudioSource* newBufferingSource = nullptr; PositionableAudioSource* newPositionableSource = nullptr; AudioSource* newMasterSource = nullptr; ScopedPointer oldResamplerSource (resamplerSource); ScopedPointer oldBufferingSource (bufferingSource); AudioSource* oldMasterSource = masterSource; if (newSource != nullptr) { newPositionableSource = newSource; if (readAheadBufferSize_ > 0) newPositionableSource = newBufferingSource = new BufferingAudioSource (newPositionableSource, false, readAheadBufferSize_, maxNumChannels); newPositionableSource->setNextReadPosition (0); if (sourceSampleRateToCorrectFor > 0) newMasterSource = newResamplerSource = new ResamplingAudioSource (newPositionableSource, false, maxNumChannels); else newMasterSource = newPositionableSource; if (isPrepared) { if (newResamplerSource != nullptr && sourceSampleRate > 0 && sampleRate > 0) newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); newMasterSource->prepareToPlay (blockSize, sampleRate); } } { const ScopedLock sl (callbackLock); source = newSource; resamplerSource = newResamplerSource; bufferingSource = newBufferingSource; masterSource = newMasterSource; positionableSource = newPositionableSource; playing = false; } if (oldMasterSource != nullptr) oldMasterSource->releaseResources(); } void AudioTransportSource::start() { if ((! playing) && masterSource != nullptr) { { const ScopedLock sl (callbackLock); playing = true; stopped = false; inputStreamEOF = false; } sendChangeMessage(); } } void AudioTransportSource::stop() { if (playing) { { const ScopedLock sl (callbackLock); playing = false; } int n = 500; while (--n >= 0 && ! stopped) Thread::sleep (2); sendChangeMessage(); } } void AudioTransportSource::setPosition (double newPosition) { if (sampleRate > 0.0) setNextReadPosition ((int64) (newPosition * sampleRate)); } double AudioTransportSource::getCurrentPosition() const { if (sampleRate > 0.0) return getNextReadPosition() / sampleRate; else return 0.0; } double AudioTransportSource::getLengthInSeconds() const { return getTotalLength() / sampleRate; } void AudioTransportSource::setNextReadPosition (int64 newPosition) { if (positionableSource != nullptr) { if (sampleRate > 0 && sourceSampleRate > 0) newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); positionableSource->setNextReadPosition (newPosition); } } int64 AudioTransportSource::getNextReadPosition() const { if (positionableSource != nullptr) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return (int64) (positionableSource->getNextReadPosition() * ratio); } return 0; } int64 AudioTransportSource::getTotalLength() const { const ScopedLock sl (callbackLock); if (positionableSource != nullptr) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return (int64) (positionableSource->getTotalLength() * ratio); } return 0; } bool AudioTransportSource::isLooping() const { const ScopedLock sl (callbackLock); return positionableSource != nullptr && positionableSource->isLooping(); } void AudioTransportSource::setGain (const float newGain) noexcept { gain = newGain; } void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { const ScopedLock sl (callbackLock); sampleRate = sampleRate_; blockSize = samplesPerBlockExpected; if (masterSource != nullptr) masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); if (resamplerSource != nullptr && sourceSampleRate > 0) resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); isPrepared = true; } void AudioTransportSource::releaseMasterResources() { const ScopedLock sl (callbackLock); if (masterSource != nullptr) masterSource->releaseResources(); isPrepared = false; } void AudioTransportSource::releaseResources() { releaseMasterResources(); } void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (callbackLock); inputStreamEOF = false; if (masterSource != nullptr && ! stopped) { masterSource->getNextAudioBlock (info); if (! playing) { // just stopped playing, so fade out the last block.. for (int i = info.buffer->getNumChannels(); --i >= 0;) info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f); if (info.numSamples > 256) info.buffer->clear (info.startSample + 256, info.numSamples - 256); } if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 && ! positionableSource->isLooping()) { playing = false; inputStreamEOF = true; sendChangeMessage(); } stopped = ! playing; for (int i = info.buffer->getNumChannels(); --i >= 0;) { info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); } } else { info.clearActiveBufferRegion(); stopped = true; } lastGain = gain; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioTransportSource.cpp ***/ /*** Start of inlined file: juce_BufferingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE class SharedBufferingAudioSourceThread : public DeletedAtShutdown, public Thread, private Timer { public: SharedBufferingAudioSourceThread() : Thread ("Audio Buffer") { } ~SharedBufferingAudioSourceThread() { stopThread (10000); clearSingletonInstance(); } juce_DeclareSingleton (SharedBufferingAudioSourceThread, false) void addSource (BufferingAudioSource* source) { const ScopedLock sl (lock); if (! sources.contains (source)) { sources.add (source); startThread(); stopTimer(); } notify(); } void removeSource (BufferingAudioSource* source) { const ScopedLock sl (lock); sources.removeValue (source); if (sources.size() == 0) startTimer (5000); } private: Array sources; CriticalSection lock; void run() { while (! threadShouldExit()) { bool busy = false; for (int i = sources.size(); --i >= 0;) { if (threadShouldExit()) return; const ScopedLock sl (lock); BufferingAudioSource* const b = sources[i]; if (b != nullptr && b->readNextBufferChunk()) busy = true; } if (! busy) wait (500); } } void timerCallback() { stopTimer(); if (sources.size() == 0) deleteInstance(); } JUCE_DECLARE_NON_COPYABLE (SharedBufferingAudioSourceThread); }; juce_ImplementSingleton (SharedBufferingAudioSourceThread) BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* source_, const bool deleteSourceWhenDeleted, const int numberOfSamplesToBuffer_, const int numberOfChannels_) : source (source_, deleteSourceWhenDeleted), numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)), numberOfChannels (numberOfChannels_), buffer (numberOfChannels_, 0), bufferValidStart (0), bufferValidEnd (0), nextPlayPos (0), wasSourceLooping (false) { jassert (source_ != nullptr); jassert (numberOfSamplesToBuffer_ > 1024); // not much point using this class if you're // not using a larger buffer.. } BufferingAudioSource::~BufferingAudioSource() { SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != nullptr) thread->removeSource (this); } void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { source->prepareToPlay (samplesPerBlockExpected, sampleRate_); sampleRate = sampleRate_; buffer.setSize (numberOfChannels, jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer)); buffer.clear(); bufferValidStart = 0; bufferValidEnd = 0; SharedBufferingAudioSourceThread::getInstance()->addSource (this); while (bufferValidEnd - bufferValidStart < jmin (((int) sampleRate_) / 4, buffer.getNumSamples() / 2)) { SharedBufferingAudioSourceThread::getInstance()->notify(); Thread::sleep (5); } } void BufferingAudioSource::releaseResources() { SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != nullptr) thread->removeSource (this); buffer.setSize (numberOfChannels, 0); source->releaseResources(); } void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (bufferStartPosLock); const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); if (validStart == validEnd) { // total cache miss info.clearActiveBufferRegion(); } else { if (validStart > 0) info.buffer->clear (info.startSample, validStart); // partial cache miss at start if (validEnd < info.numSamples) info.buffer->clear (info.startSample + validEnd, info.numSamples - validEnd); // partial cache miss at end if (validStart < validEnd) { for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) { const int startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); const int endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); if (startBufferIndex < endBufferIndex) { info.buffer->copyFrom (chan, info.startSample + validStart, buffer, chan, startBufferIndex, validEnd - validStart); } else { const int initialSize = buffer.getNumSamples() - startBufferIndex; info.buffer->copyFrom (chan, info.startSample + validStart, buffer, chan, startBufferIndex, initialSize); info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, buffer, chan, 0, (validEnd - validStart) - initialSize); } } } nextPlayPos += info.numSamples; if (source->isLooping() && nextPlayPos > 0) nextPlayPos %= source->getTotalLength(); } SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != nullptr) thread->notify(); } int64 BufferingAudioSource::getNextReadPosition() const { return (source->isLooping() && nextPlayPos > 0) ? nextPlayPos % source->getTotalLength() : nextPlayPos; } void BufferingAudioSource::setNextReadPosition (int64 newPosition) { const ScopedLock sl (bufferStartPosLock); nextPlayPos = newPosition; SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != nullptr) thread->notify(); } bool BufferingAudioSource::readNextBufferChunk() { int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; { const ScopedLock sl (bufferStartPosLock); if (wasSourceLooping != isLooping()) { wasSourceLooping = isLooping(); bufferValidStart = 0; bufferValidEnd = 0; } newBVS = jmax ((int64) 0, nextPlayPos); newBVE = newBVS + buffer.getNumSamples() - 4; sectionToReadStart = 0; sectionToReadEnd = 0; const int maxChunkSize = 2048; if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) { newBVE = jmin (newBVE, newBVS + maxChunkSize); sectionToReadStart = newBVS; sectionToReadEnd = newBVE; bufferValidStart = 0; bufferValidEnd = 0; } else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 || std::abs ((int) (newBVE - bufferValidEnd)) > 512) { newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); sectionToReadStart = bufferValidEnd; sectionToReadEnd = newBVE; bufferValidStart = newBVS; bufferValidEnd = jmin (bufferValidEnd, newBVE); } } if (sectionToReadStart != sectionToReadEnd) { const int bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); const int bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); if (bufferIndexStart < bufferIndexEnd) { readBufferSection (sectionToReadStart, (int) (sectionToReadEnd - sectionToReadStart), bufferIndexStart); } else { const int initialSize = buffer.getNumSamples() - bufferIndexStart; readBufferSection (sectionToReadStart, initialSize, bufferIndexStart); readBufferSection (sectionToReadStart + initialSize, (int) (sectionToReadEnd - sectionToReadStart) - initialSize, 0); } const ScopedLock sl2 (bufferStartPosLock); bufferValidStart = newBVS; bufferValidEnd = newBVE; return true; } else { return false; } } void BufferingAudioSource::readBufferSection (const int64 start, const int length, const int bufferOffset) { if (source->getNextReadPosition() != start) source->setNextReadPosition (start); AudioSourceChannelInfo info; info.buffer = &buffer; info.startSample = bufferOffset; info.numSamples = length; source->getNextAudioBlock (info); } END_JUCE_NAMESPACE /*** End of inlined file: juce_BufferingAudioSource.cpp ***/ /*** Start of inlined file: juce_ChannelRemappingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, const bool deleteSourceWhenDeleted) : source (source_, deleteSourceWhenDeleted), requiredNumberOfChannels (2), buffer (2, 16) { remappedInfo.buffer = &buffer; remappedInfo.startSample = 0; } ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) { const ScopedLock sl (lock); requiredNumberOfChannels = requiredNumberOfChannels_; } void ChannelRemappingAudioSource::clearAllMappings() { const ScopedLock sl (lock); remappedInputs.clear(); remappedOutputs.clear(); } void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) { const ScopedLock sl (lock); while (remappedInputs.size() < destIndex) remappedInputs.add (-1); remappedInputs.set (destIndex, sourceIndex); } void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) { const ScopedLock sl (lock); while (remappedOutputs.size() < sourceIndex) remappedOutputs.add (-1); remappedOutputs.set (sourceIndex, destIndex); } int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const { const ScopedLock sl (lock); if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) return remappedInputs.getUnchecked (inputChannelIndex); return -1; } int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const { const ScopedLock sl (lock); if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) return remappedOutputs .getUnchecked (outputChannelIndex); return -1; } void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { source->prepareToPlay (samplesPerBlockExpected, sampleRate); } void ChannelRemappingAudioSource::releaseResources() { source->releaseResources(); } void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) { const ScopedLock sl (lock); buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); const int numChans = bufferToFill.buffer->getNumChannels(); int i; for (i = 0; i < buffer.getNumChannels(); ++i) { const int remappedChan = getRemappedInputChannel (i); if (remappedChan >= 0 && remappedChan < numChans) { buffer.copyFrom (i, 0, *bufferToFill.buffer, remappedChan, bufferToFill.startSample, bufferToFill.numSamples); } else { buffer.clear (i, 0, bufferToFill.numSamples); } } remappedInfo.numSamples = bufferToFill.numSamples; source->getNextAudioBlock (remappedInfo); bufferToFill.clearActiveBufferRegion(); for (i = 0; i < requiredNumberOfChannels; ++i) { const int remappedChan = getRemappedOutputChannel (i); if (remappedChan >= 0 && remappedChan < numChans) { bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, buffer, i, 0, bufferToFill.numSamples); } } } XmlElement* ChannelRemappingAudioSource::createXml() const { XmlElement* e = new XmlElement ("MAPPINGS"); String ins, outs; int i; const ScopedLock sl (lock); for (i = 0; i < remappedInputs.size(); ++i) ins << remappedInputs.getUnchecked(i) << ' '; for (i = 0; i < remappedOutputs.size(); ++i) outs << remappedOutputs.getUnchecked(i) << ' '; e->setAttribute ("inputs", ins.trimEnd()); e->setAttribute ("outputs", outs.trimEnd()); return e; } void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) { if (e.hasTagName ("MAPPINGS")) { const ScopedLock sl (lock); clearAllMappings(); StringArray ins, outs; ins.addTokens (e.getStringAttribute ("inputs"), false); outs.addTokens (e.getStringAttribute ("outputs"), false); int i; for (i = 0; i < ins.size(); ++i) remappedInputs.add (ins[i].getIntValue()); for (i = 0; i < outs.size(); ++i) remappedOutputs.add (outs[i].getIntValue()); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChannelRemappingAudioSource.cpp ***/ /*** Start of inlined file: juce_IIRFilterAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) : input (inputSource, deleteInputWhenDeleted) { jassert (inputSource != nullptr); for (int i = 2; --i >= 0;) iirFilters.add (new IIRFilter()); } IIRFilterAudioSource::~IIRFilterAudioSource() {} void IIRFilterAudioSource::setFilterParameters (const IIRFilter& newSettings) { for (int i = iirFilters.size(); --i >= 0;) iirFilters.getUnchecked(i)->copyCoefficientsFrom (newSettings); } void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { input->prepareToPlay (samplesPerBlockExpected, sampleRate); for (int i = iirFilters.size(); --i >= 0;) iirFilters.getUnchecked(i)->reset(); } void IIRFilterAudioSource::releaseResources() { input->releaseResources(); } void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) { input->getNextAudioBlock (bufferToFill); const int numChannels = bufferToFill.buffer->getNumChannels(); while (numChannels > iirFilters.size()) iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); for (int i = 0; i < numChannels; ++i) iirFilters.getUnchecked(i) ->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample), bufferToFill.numSamples); } END_JUCE_NAMESPACE /*** End of inlined file: juce_IIRFilterAudioSource.cpp ***/ /*** Start of inlined file: juce_ReverbAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) : input (inputSource, deleteInputWhenDeleted), bypass (false) { jassert (inputSource != nullptr); } ReverbAudioSource::~ReverbAudioSource() {} void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { const ScopedLock sl (lock); input->prepareToPlay (samplesPerBlockExpected, sampleRate); reverb.setSampleRate (sampleRate); } void ReverbAudioSource::releaseResources() {} void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) { const ScopedLock sl (lock); input->getNextAudioBlock (bufferToFill); if (! bypass) { float* const firstChannel = bufferToFill.buffer->getSampleData (0, bufferToFill.startSample); if (bufferToFill.buffer->getNumChannels() > 1) { reverb.processStereo (firstChannel, bufferToFill.buffer->getSampleData (1, bufferToFill.startSample), bufferToFill.numSamples); } else { reverb.processMono (firstChannel, bufferToFill.numSamples); } } } void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) { const ScopedLock sl (lock); reverb.setParameters (newParams); } void ReverbAudioSource::setBypassed (bool b) noexcept { if (bypass != b) { const ScopedLock sl (lock); bypass = b; reverb.reset(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ReverbAudioSource.cpp ***/ /*** Start of inlined file: juce_MixerAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE MixerAudioSource::MixerAudioSource() : tempBuffer (2, 0), currentSampleRate (0.0), bufferSizeExpected (0) { } MixerAudioSource::~MixerAudioSource() { removeAllInputs(); } void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) { if (input != nullptr && ! inputs.contains (input)) { double localRate; int localBufferSize; { const ScopedLock sl (lock); localRate = currentSampleRate; localBufferSize = bufferSizeExpected; } if (localRate > 0.0) input->prepareToPlay (localBufferSize, localRate); const ScopedLock sl (lock); inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); inputs.add (input); } } void MixerAudioSource::removeInputSource (AudioSource* input, const bool deleteInput) { if (input != nullptr) { int index; { const ScopedLock sl (lock); index = inputs.indexOf (input); if (index >= 0) { inputsToDelete.shiftBits (index, 1); inputs.remove (index); } } if (index >= 0) { input->releaseResources(); if (deleteInput) delete input; } } } void MixerAudioSource::removeAllInputs() { OwnedArray toDelete; { const ScopedLock sl (lock); for (int i = inputs.size(); --i >= 0;) if (inputsToDelete[i]) toDelete.add (inputs.getUnchecked(i)); } } void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { tempBuffer.setSize (2, samplesPerBlockExpected); const ScopedLock sl (lock); currentSampleRate = sampleRate; bufferSizeExpected = samplesPerBlockExpected; for (int i = inputs.size(); --i >= 0;) inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); } void MixerAudioSource::releaseResources() { const ScopedLock sl (lock); for (int i = inputs.size(); --i >= 0;) inputs.getUnchecked(i)->releaseResources(); tempBuffer.setSize (2, 0); currentSampleRate = 0; bufferSizeExpected = 0; } void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (lock); if (inputs.size() > 0) { inputs.getUnchecked(0)->getNextAudioBlock (info); if (inputs.size() > 1) { tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), info.buffer->getNumSamples()); AudioSourceChannelInfo info2; info2.buffer = &tempBuffer; info2.numSamples = info.numSamples; info2.startSample = 0; for (int i = 1; i < inputs.size(); ++i) { inputs.getUnchecked(i)->getNextAudioBlock (info2); for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); } } } else { info.clearActiveBufferRegion(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_MixerAudioSource.cpp ***/ /*** Start of inlined file: juce_ResamplingAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted, const int numChannels_) : input (inputSource, deleteInputWhenDeleted), ratio (1.0), lastRatio (1.0), buffer (numChannels_, 0), sampsInBuffer (0), numChannels (numChannels_) { jassert (input != nullptr); } ResamplingAudioSource::~ResamplingAudioSource() {} void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) { jassert (samplesInPerOutputSample > 0); const SpinLock::ScopedLockType sl (ratioLock); ratio = jmax (0.0, samplesInPerOutputSample); } void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { const SpinLock::ScopedLockType sl (ratioLock); input->prepareToPlay (samplesPerBlockExpected, sampleRate); buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32); buffer.clear(); sampsInBuffer = 0; bufferPos = 0; subSampleOffset = 0.0; filterStates.calloc (numChannels); srcBuffers.calloc (numChannels); destBuffers.calloc (numChannels); createLowPass (ratio); resetFilters(); } void ResamplingAudioSource::releaseResources() { input->releaseResources(); buffer.setSize (numChannels, 0); } void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { double localRatio; { const SpinLock::ScopedLockType sl (ratioLock); localRatio = ratio; } if (lastRatio != localRatio) { createLowPass (localRatio); lastRatio = localRatio; } const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 2; int bufferSize = buffer.getNumSamples(); if (bufferSize < sampsNeeded + 8) { bufferPos %= bufferSize; bufferSize = sampsNeeded + 32; buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); } bufferPos %= bufferSize; int endOfBufferPos = bufferPos + sampsInBuffer; const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); while (sampsNeeded > sampsInBuffer) { endOfBufferPos %= bufferSize; int numToDo = jmin (sampsNeeded - sampsInBuffer, bufferSize - endOfBufferPos); AudioSourceChannelInfo readInfo; readInfo.buffer = &buffer; readInfo.numSamples = numToDo; readInfo.startSample = endOfBufferPos; input->getNextAudioBlock (readInfo); if (localRatio > 1.0001) { // for down-sampling, pre-apply the filter.. for (int i = channelsToProcess; --i >= 0;) applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]); } sampsInBuffer += numToDo; endOfBufferPos += numToDo; } for (int channel = 0; channel < channelsToProcess; ++channel) { destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample); srcBuffers[channel] = buffer.getSampleData (channel, 0); } int nextPos = (bufferPos + 1) % bufferSize; for (int m = info.numSamples; --m >= 0;) { const float alpha = (float) subSampleOffset; const float invAlpha = 1.0f - alpha; for (int channel = 0; channel < channelsToProcess; ++channel) *destBuffers[channel]++ = srcBuffers[channel][bufferPos] * invAlpha + srcBuffers[channel][nextPos] * alpha; subSampleOffset += localRatio; jassert (sampsInBuffer > 0); while (subSampleOffset >= 1.0) { if (++bufferPos >= bufferSize) bufferPos = 0; --sampsInBuffer; nextPos = (bufferPos + 1) % bufferSize; subSampleOffset -= 1.0; } } if (localRatio < 0.9999) { // for up-sampling, apply the filter after transposing.. for (int i = channelsToProcess; --i >= 0;) applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]); } else if (localRatio <= 1.0001) { // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities for (int i = channelsToProcess; --i >= 0;) { const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1); FilterState& fs = filterStates[i]; if (info.numSamples > 1) { fs.y2 = fs.x2 = *(endOfBuffer - 1); } else { fs.y2 = fs.y1; fs.x2 = fs.x1; } fs.y1 = fs.x1 = *endOfBuffer; } } jassert (sampsInBuffer >= 0); } void ResamplingAudioSource::createLowPass (const double frequencyRatio) { const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio : 0.5 * frequencyRatio; const double n = 1.0 / std::tan (double_Pi * jmax (0.001, proportionalRate)); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setFilterCoefficients (c1, c1 * 2.0f, c1, 1.0, c1 * 2.0 * (1.0 - nSquared), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) { const double a = 1.0 / c4; c1 *= a; c2 *= a; c3 *= a; c5 *= a; c6 *= a; coefficients[0] = c1; coefficients[1] = c2; coefficients[2] = c3; coefficients[3] = c4; coefficients[4] = c5; coefficients[5] = c6; } void ResamplingAudioSource::resetFilters() { filterStates.clear (numChannels); } void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) { while (--num >= 0) { const double in = *samples; double out = coefficients[0] * in + coefficients[1] * fs.x1 + coefficients[2] * fs.x2 - coefficients[4] * fs.y1 - coefficients[5] * fs.y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif fs.x2 = fs.x1; fs.x1 = in; fs.y2 = fs.y1; fs.y1 = out; *samples++ = (float) out; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ResamplingAudioSource.cpp ***/ /*** Start of inlined file: juce_ToneGeneratorAudioSource.cpp ***/ BEGIN_JUCE_NAMESPACE ToneGeneratorAudioSource::ToneGeneratorAudioSource() : frequency (1000.0), sampleRate (44100.0), currentPhase (0.0), phasePerSample (0.0), amplitude (0.5f) { } ToneGeneratorAudioSource::~ToneGeneratorAudioSource() { } void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) { amplitude = newAmplitude; } void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) { frequency = newFrequencyHz; phasePerSample = 0.0; } void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate_) { currentPhase = 0.0; phasePerSample = 0.0; sampleRate = sampleRate_; } void ToneGeneratorAudioSource::releaseResources() { } void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { if (phasePerSample == 0.0) phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); for (int i = 0; i < info.numSamples; ++i) { const float sample = amplitude * (float) std::sin (currentPhase); currentPhase += phasePerSample; for (int j = info.buffer->getNumChannels(); --j >= 0;) *info.buffer->getSampleData (j, info.startSample + i) = sample; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToneGeneratorAudioSource.cpp ***/ /*** Start of inlined file: juce_AudioDeviceManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() : sampleRate (0), bufferSize (0), useDefaultInputChannels (true), useDefaultOutputChannels (true) { } bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const { return outputDeviceName == other.outputDeviceName && inputDeviceName == other.inputDeviceName && sampleRate == other.sampleRate && bufferSize == other.bufferSize && inputChannels == other.inputChannels && useDefaultInputChannels == other.useDefaultInputChannels && outputChannels == other.outputChannels && useDefaultOutputChannels == other.useDefaultOutputChannels; } AudioDeviceManager::AudioDeviceManager() : numInputChansNeeded (0), numOutputChansNeeded (2), listNeedsScanning (true), useInputNames (false), inputLevelMeasurementEnabledCount (0), inputLevel (0), tempBuffer (2, 2), cpuUsageMs (0), timeToCpuScale (0) { callbackHandler.owner = this; } AudioDeviceManager::~AudioDeviceManager() { currentAudioDevice = nullptr; defaultMidiOutput = nullptr; } void AudioDeviceManager::createDeviceTypesIfNeeded() { if (availableDeviceTypes.size() == 0) { createAudioDeviceTypes (availableDeviceTypes); while (lastDeviceTypeConfigs.size() < availableDeviceTypes.size()) lastDeviceTypeConfigs.add (new AudioDeviceSetup()); if (availableDeviceTypes.size() > 0) currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); } } const OwnedArray & AudioDeviceManager::getAvailableDeviceTypes() { scanDevicesIfNeeded(); return availableDeviceTypes; } static void addIfNotNull (OwnedArray & list, AudioIODeviceType* const device) { if (device != nullptr) list.add (device); } void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & list) { addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); } const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, const XmlElement* const e, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != nullptr && e->hasTagName ("DEVICESETUP")) { lastExplicitSettings = new XmlElement (*e); String error; AudioDeviceSetup setup; if (preferredSetupOptions != nullptr) setup = *preferredSetupOptions; if (e->getStringAttribute ("audioDeviceName").isNotEmpty()) { setup.inputDeviceName = setup.outputDeviceName = e->getStringAttribute ("audioDeviceName"); } else { setup.inputDeviceName = e->getStringAttribute ("audioInputDeviceName"); setup.outputDeviceName = e->getStringAttribute ("audioOutputDeviceName"); } currentDeviceType = e->getStringAttribute ("deviceType"); if (currentDeviceType.isEmpty()) { AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName); if (type != nullptr) currentDeviceType = type->getTypeName(); else if (availableDeviceTypes.size() > 0) currentDeviceType = availableDeviceTypes[0]->getTypeName(); } setup.bufferSize = e->getIntAttribute ("audioDeviceBufferSize"); setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); setup.inputChannels.parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (*e, c, "MIDIINPUT") midiInsFromXml.add (c->getStringAttribute ("name")); const StringArray allMidiIns (MidiInput::getDevices()); for (int i = allMidiIns.size(); --i >= 0;) setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); if (error.isNotEmpty() && selectDefaultDeviceOnFailure) error = initialise (numInputChannelsNeeded, numOutputChannelsNeeded, 0, false, preferredDefaultDeviceName); setDefaultMidiOutput (e->getStringAttribute ("defaultMidiOutput")); return error; } else { AudioDeviceSetup setup; if (preferredSetupOptions != nullptr) { setup = *preferredSetupOptions; } else if (preferredDefaultDeviceName.isNotEmpty()) { for (int j = availableDeviceTypes.size(); --j >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); StringArray outs (type->getDeviceNames (false)); int i; for (i = 0; i < outs.size(); ++i) { if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.outputDeviceName = outs[i]; break; } } StringArray ins (type->getDeviceNames (true)); for (i = 0; i < ins.size(); ++i) { if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.inputDeviceName = ins[i]; break; } } } } insertDefaultDeviceNames (setup); return setAudioDeviceSetup (setup, false); } } void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const { AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type != nullptr) { if (setup.outputDeviceName.isEmpty()) setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; if (setup.inputDeviceName.isEmpty()) setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; } } XmlElement* AudioDeviceManager::createStateXml() const { return lastExplicitSettings != nullptr ? new XmlElement (*lastExplicitSettings) : nullptr; } void AudioDeviceManager::scanDevicesIfNeeded() { if (listNeedsScanning) { listNeedsScanning = false; createDeviceTypesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) availableDeviceTypes.getUnchecked(i)->scanForDevices(); } } AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) { scanDevicesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); if ((inputName.isNotEmpty() && type->getDeviceNames (true).contains (inputName, true)) || (outputName.isNotEmpty() && type->getDeviceNames (false).contains (outputName, true))) { return type; } } return nullptr; } void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { setup = currentSetup; } void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = nullptr; currentSetup.inputDeviceName = String::empty; currentSetup.outputDeviceName = String::empty; } void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, const bool treatAsChosenDevice) { for (int i = 0; i < availableDeviceTypes.size(); ++i) { if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type && currentDeviceType != type) { currentDeviceType = type; AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); insertDefaultDeviceNames (s); setAudioDeviceSetup (s, treatAsChosenDevice); sendChangeMessage(); break; } } } AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const { for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes[i]->getTypeName() == currentDeviceType) return availableDeviceTypes[i]; return availableDeviceTypes[0]; } const String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, const bool treatAsChosenDevice) { jassert (&newSetup != ¤tSetup); // this will have no effect if (newSetup == currentSetup && currentAudioDevice != nullptr) return String::empty; if (! (newSetup == currentSetup)) sendChangeMessage(); stopDevice(); const String newInputDeviceName (numInputChansNeeded == 0 ? String::empty : newSetup.inputDeviceName); const String newOutputDeviceName (numOutputChansNeeded == 0 ? String::empty : newSetup.outputDeviceName); String error; AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) { deleteCurrentDevice(); if (treatAsChosenDevice) updateXml(); return String::empty; } if (currentSetup.inputDeviceName != newInputDeviceName || currentSetup.outputDeviceName != newOutputDeviceName || currentAudioDevice == nullptr) { deleteCurrentDevice(); scanDevicesIfNeeded(); if (newOutputDeviceName.isNotEmpty() && ! type->getDeviceNames (false).contains (newOutputDeviceName)) { return "No such device: " + newOutputDeviceName; } if (newInputDeviceName.isNotEmpty() && ! type->getDeviceNames (true).contains (newInputDeviceName)) { return "No such device: " + newInputDeviceName; } currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); if (currentAudioDevice == nullptr) error = "Can't open the audio device!\n\nThis may be because another application is currently using the same device - if so, you should close any other applications and try again!"; else error = currentAudioDevice->getLastError(); if (error.isNotEmpty()) { deleteCurrentDevice(); return error; } if (newSetup.useDefaultInputChannels) { inputChannels.clear(); inputChannels.setRange (0, numInputChansNeeded, true); } if (newSetup.useDefaultOutputChannels) { outputChannels.clear(); outputChannels.setRange (0, numOutputChansNeeded, true); } if (newInputDeviceName.isEmpty()) inputChannels.clear(); if (newOutputDeviceName.isEmpty()) outputChannels.clear(); } if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; currentSetup = newSetup; currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); currentSetup.bufferSize = chooseBestBufferSize (newSetup.bufferSize); error = currentAudioDevice->open (inputChannels, outputChannels, currentSetup.sampleRate, currentSetup.bufferSize); if (error.isEmpty()) { currentDeviceType = currentAudioDevice->getTypeName(); currentAudioDevice->start (&callbackHandler); currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) *(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; if (treatAsChosenDevice) updateXml(); } else { deleteCurrentDevice(); } return error; } double AudioDeviceManager::chooseBestSampleRate (double rate) const { jassert (currentAudioDevice != nullptr); if (rate > 0) for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) if (currentAudioDevice->getSampleRate (i) == rate) return rate; double lowestAbove44 = 0.0; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 > 0.0) return lowestAbove44; return currentAudioDevice->getSampleRate (0); } int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const { jassert (currentAudioDevice != nullptr); if (bufferSize > 0) for (int i = currentAudioDevice->getNumBufferSizesAvailable(); --i >= 0;) if (currentAudioDevice->getBufferSizeSamples(i) == bufferSize) return bufferSize; return currentAudioDevice->getDefaultBufferSize(); } void AudioDeviceManager::stopDevice() { if (currentAudioDevice != nullptr) currentAudioDevice->stop(); testSound = nullptr; } void AudioDeviceManager::closeAudioDevice() { stopDevice(); currentAudioDevice = nullptr; } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == nullptr) { if (currentSetup.inputDeviceName.isEmpty() && currentSetup.outputDeviceName.isEmpty()) { // This method will only reload the last device that was running // before closeAudioDevice() was called - you need to actually open // one first, with setAudioDevice(). jassertfalse; return; } AudioDeviceSetup s (currentSetup); setAudioDeviceSetup (s, false); } } void AudioDeviceManager::updateXml() { lastExplicitSettings = new XmlElement ("DEVICESETUP"); lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); if (currentAudioDevice != nullptr) { lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); if (! currentSetup.useDefaultInputChannels) lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); if (! currentSetup.useDefaultOutputChannels) lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) { XmlElement* const m = lastExplicitSettings->createNewChildElement ("MIDIINPUT"); m->setAttribute ("name", enabledMidiInputs[i]->getName()); } if (midiInsFromXml.size() > 0) { // Add any midi devices that have been enabled before, but which aren't currently // open because the device has been disconnected. const StringArray availableMidiDevices (MidiInput::getDevices()); for (int i = 0; i < midiInsFromXml.size(); ++i) { if (! availableMidiDevices.contains (midiInsFromXml[i], true)) { XmlElement* const m = lastExplicitSettings->createNewChildElement ("MIDIINPUT"); m->setAttribute ("name", midiInsFromXml[i]); } } } if (defaultMidiOutputName.isNotEmpty()) lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); } void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) { { const ScopedLock sl (audioCallbackLock); if (callbacks.contains (newCallback)) return; } if (currentAudioDevice != nullptr && newCallback != nullptr) newCallback->audioDeviceAboutToStart (currentAudioDevice); const ScopedLock sl (audioCallbackLock); callbacks.add (newCallback); } void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) { if (callbackToRemove != nullptr) { bool needsDeinitialising = currentAudioDevice != nullptr; { const ScopedLock sl (audioCallbackLock); needsDeinitialising = needsDeinitialising && callbacks.contains (callbackToRemove); callbacks.removeValue (callbackToRemove); } if (needsDeinitialising) callbackToRemove->audioDeviceStopped(); } } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, int numInputChannels, float** outputChannelData, int numOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); if (inputLevelMeasurementEnabledCount > 0 && numInputChannels > 0) { for (int j = 0; j < numSamples; ++j) { float s = 0; for (int i = 0; i < numInputChannels; ++i) s += std::abs (inputChannelData[i][j]); s /= numInputChannels; const double decayFactor = 0.99992; if (s > inputLevel) inputLevel = s; else if (inputLevel > 0.001f) inputLevel *= decayFactor; else inputLevel = 0; } } else { inputLevel = 0; } if (callbacks.size() > 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); float** const tempChans = tempBuffer.getArrayOfChannels(); for (int i = callbacks.size(); --i > 0;) { callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, tempChans, numOutputChannels, numSamples); for (int chan = 0; chan < numOutputChannels; ++chan) { const float* const src = tempChans [chan]; float* const dst = outputChannelData [chan]; if (src != nullptr && dst != nullptr) for (int j = 0; j < numSamples; ++j) dst[j] += src[j]; } } const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; const double filterAmount = 0.2; cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); } else { for (int i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * numSamples); } if (testSound != nullptr) { const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); const float* const src = testSound->getSampleData (0, testSoundPosition); for (int i = 0; i < numOutputChannels; ++i) for (int j = 0; j < numSamps; ++j) outputChannelData [i][j] += src[j]; testSoundPosition += numSamps; if (testSoundPosition >= testSound->getNumSamples()) testSound = nullptr; } } void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) { cpuUsageMs = 0; const double sampleRate = device->getCurrentSampleRate(); const int blockSize = device->getCurrentBufferSizeSamples(); if (sampleRate > 0.0 && blockSize > 0) { const double msPerBlock = 1000.0 * blockSize / sampleRate; timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; } { const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); } sendChangeMessage(); } void AudioDeviceManager::audioDeviceStoppedInt() { cpuUsageMs = 0; timeToCpuScale = 0; sendChangeMessage(); const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceStopped(); } double AudioDeviceManager::getCpuUsage() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) { if (enabled != isMidiInputEnabled (name)) { if (enabled) { const int index = MidiInput::getDevices().indexOf (name); if (index >= 0) { MidiInput* const midiIn = MidiInput::openDevice (index, &callbackHandler); if (midiIn != nullptr) { enabledMidiInputs.add (midiIn); midiIn->start(); } } } else { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) enabledMidiInputs.remove (i); } updateXml(); sendChangeMessage(); } } bool AudioDeviceManager::isMidiInputEnabled (const String& name) const { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) return true; return false; } void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callbackToAdd) { removeMidiInputCallback (name, callbackToAdd); if (name.isEmpty() || isMidiInputEnabled (name)) { const ScopedLock sl (midiCallbackLock); midiCallbacks.add (callbackToAdd); midiCallbackDevices.add (name); } } void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) { for (int i = midiCallbacks.size(); --i >= 0;) { if (midiCallbackDevices[i] == name && midiCallbacks.getUnchecked(i) == callbackToRemove) { const ScopedLock sl (midiCallbackLock); midiCallbacks.remove (i); midiCallbackDevices.remove (i); } } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const bool isDefaultSource = (source == nullptr || source == enabledMidiInputs.getFirst()); const ScopedLock sl (midiCallbackLock); for (int i = midiCallbackDevices.size(); --i >= 0;) { const String name (midiCallbackDevices[i]); if ((isDefaultSource && name.isEmpty()) || (name.isNotEmpty() && name == source->getName())) midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); } } } void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) { if (defaultMidiOutputName != deviceName) { Array oldCallbacks; { const ScopedLock sl (audioCallbackLock); oldCallbacks = callbacks; callbacks.clear(); } if (currentAudioDevice != nullptr) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceStopped(); defaultMidiOutput = nullptr; defaultMidiOutputName = deviceName; if (deviceName.isNotEmpty()) defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); if (currentAudioDevice != nullptr) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); { const ScopedLock sl (audioCallbackLock); callbacks = oldCallbacks; } updateXml(); sendChangeMessage(); } } void AudioDeviceManager::CallbackHandler::audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, float** outputChannelData, int numOutputChannels, int numSamples) { owner->audioDeviceIOCallbackInt (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); } void AudioDeviceManager::CallbackHandler::audioDeviceAboutToStart (AudioIODevice* device) { owner->audioDeviceAboutToStartInt (device); } void AudioDeviceManager::CallbackHandler::audioDeviceStopped() { owner->audioDeviceStoppedInt(); } void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) { owner->handleIncomingMidiMessageInt (source, message); } void AudioDeviceManager::playTestSound() { { // cunningly nested to swap, unlock and delete in that order. ScopedPointer oldSound; { const ScopedLock sl (audioCallbackLock); oldSound = testSound; } } testSoundPosition = 0; if (currentAudioDevice != nullptr) { const double sampleRate = currentAudioDevice->getCurrentSampleRate(); const int soundLength = (int) sampleRate; AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); float* samples = newSound->getSampleData (0); const double frequency = MidiMessage::getMidiNoteInHertz (80); const float amplitude = 0.5f; const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); for (int i = 0; i < soundLength; ++i) samples[i] = amplitude * (float) std::sin (i * phasePerSample); newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); const ScopedLock sl (audioCallbackLock); testSound = newSound; } } void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) { const ScopedLock sl (audioCallbackLock); if (enableMeasurement) ++inputLevelMeasurementEnabledCount; else --inputLevelMeasurementEnabledCount; inputLevel = 0; } double AudioDeviceManager::getCurrentInputLevel() const { jassert (inputLevelMeasurementEnabledCount > 0); // you need to call enableInputLevelMeasurement() before using this! return inputLevel; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioDeviceManager.cpp ***/ /*** Start of inlined file: juce_AudioIODevice.cpp ***/ BEGIN_JUCE_NAMESPACE AudioIODevice::AudioIODevice (const String& deviceName, const String& typeName_) : name (deviceName), typeName (typeName_) { } AudioIODevice::~AudioIODevice() { } bool AudioIODevice::hasControlPanel() const { return false; } bool AudioIODevice::showControlPanel() { jassertfalse; // this should only be called for devices which return true from // their hasControlPanel() method. return false; } void AudioIODeviceCallback::audioDeviceError (const String&) {} END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioIODevice.cpp ***/ /*** Start of inlined file: juce_AudioIODeviceType.cpp ***/ BEGIN_JUCE_NAMESPACE AudioIODeviceType::AudioIODeviceType (const String& name) : typeName (name) { } AudioIODeviceType::~AudioIODeviceType() { } #if ! JUCE_MAC AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } #endif #if ! JUCE_IOS AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } #endif #if ! (JUCE_WINDOWS && JUCE_WASAPI) AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI() { return nullptr; } #endif #if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } #endif #if ! (JUCE_WINDOWS && JUCE_ASIO) AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } #endif #if ! (JUCE_LINUX && JUCE_ALSA) AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } #endif #if ! (JUCE_LINUX && JUCE_JACK) AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } #endif #if ! JUCE_ANDROID AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioIODeviceType.cpp ***/ /*** Start of inlined file: juce_AudioDataConverters.cpp ***/ BEGIN_JUCE_NAMESPACE void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { ByteOrder::littleEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; ByteOrder::littleEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); } } } void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { ByteOrder::bigEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; ByteOrder::bigEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); } } } void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; char* intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { for (int i = 0; i < numSamples; ++i) { *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } else { intData += destBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! char* d = static_cast (dest); for (int i = 0; i < numSamples; ++i) { *(float*) d = source[i]; #if JUCE_BIG_ENDIAN *(uint32*) d = ByteOrder::swap (*(uint32*) d); #endif d += destBytesPerSample; } } void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! char* d = static_cast (dest); for (int i = 0; i < numSamples; ++i) { *(float*) d = source[i]; #if JUCE_LITTLE_ENDIAN *(uint32*) d = ByteOrder::swap (*(uint32*) d); #endif d += destBytesPerSample; } } void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); } } } void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); } } } void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); } } } void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); } } } void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); } } } void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; const char* intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { for (int i = 0; i < numSamples; ++i) { dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); intData += srcBytesPerSample; } } else { intData += srcBytesPerSample * numSamples; for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); } } } void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const char* s = static_cast (source); for (int i = 0; i < numSamples; ++i) { dest[i] = *(float*)s; #if JUCE_BIG_ENDIAN uint32* const d = (uint32*) (dest + i); *d = ByteOrder::swap (*d); #endif s += srcBytesPerSample; } } void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const char* s = static_cast (source); for (int i = 0; i < numSamples; ++i) { dest[i] = *(float*)s; #if JUCE_LITTLE_ENDIAN uint32* const d = (uint32*) (dest + i); *d = ByteOrder::swap (*d); #endif s += srcBytesPerSample; } } void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, const float* const source, void* const dest, const int numSamples) { switch (destFormat) { case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; default: jassertfalse; break; } } void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, const void* const source, float* const dest, const int numSamples) { switch (sourceFormat) { case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; default: jassertfalse; break; } } void AudioDataConverters::interleaveSamples (const float** const source, float* const dest, const int numSamples, const int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { int i = chan; const float* src = source [chan]; for (int j = 0; j < numSamples; ++j) { dest [i] = src [j]; i += numChannels; } } } void AudioDataConverters::deinterleaveSamples (const float* const source, float** const dest, const int numSamples, const int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { int i = chan; float* dst = dest [chan]; for (int j = 0; j < numSamples; ++j) { dst [j] = source [i]; i += numChannels; } } } #if JUCE_UNIT_TESTS class AudioConversionTests : public UnitTest { public: AudioConversionTests() : UnitTest ("Audio data conversion") {} template struct Test5 { static void test (UnitTest& unitTest) { test (unitTest, false); test (unitTest, true); } static void test (UnitTest& unitTest, bool inPlace) { const int numSamples = 2048; int32 original [numSamples], converted [numSamples], reversed [numSamples]; { AudioData::Pointer d (original); bool clippingFailed = false; for (int i = 0; i < numSamples / 2; ++i) { d.setAsFloat (Random::getSystemRandom().nextFloat() * 2.2f - 1.1f); if (! d.isFloatingPoint()) clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; ++d; d.setAsInt32 (Random::getSystemRandom().nextInt()); ++d; } unitTest.expect (! clippingFailed); } // convert data from the source to dest format.. ScopedPointer conv (new AudioData::ConverterInstance , AudioData::Pointer >()); conv->convertSamples (inPlace ? reversed : converted, original, numSamples); // ..and back again.. conv = new AudioData::ConverterInstance , AudioData::Pointer >(); if (! inPlace) zeromem (reversed, sizeof (reversed)); conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); { int biggestDiff = 0; AudioData::Pointer d1 (original); AudioData::Pointer d2 (reversed); const int errorMargin = 2 * AudioData::Pointer::get32BitResolution() + AudioData::Pointer::get32BitResolution(); for (int i = 0; i < numSamples; ++i) { biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); ++d1; ++d2; } unitTest.expect (biggestDiff <= errorMargin); } } }; template struct Test3 { static void test (UnitTest& unitTest) { Test5 ::test (unitTest); Test5 ::test (unitTest); } }; template struct Test2 { static void test (UnitTest& unitTest) { Test3 ::test (unitTest); Test3 ::test (unitTest); Test3 ::test (unitTest); Test3 ::test (unitTest); Test3 ::test (unitTest); Test3 ::test (unitTest); } }; template struct Test1 { static void test (UnitTest& unitTest) { Test2 ::test (unitTest); Test2 ::test (unitTest); } }; void runTest() { beginTest ("Round-trip conversion: Int8"); Test1 ::test (*this); beginTest ("Round-trip conversion: Int16"); Test1 ::test (*this); beginTest ("Round-trip conversion: Int24"); Test1 ::test (*this); beginTest ("Round-trip conversion: Int32"); Test1 ::test (*this); beginTest ("Round-trip conversion: Float32"); Test1 ::test (*this); } }; static AudioConversionTests audioConversionUnitTests; #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioDataConverters.cpp ***/ /*** Start of inlined file: juce_AudioSampleBuffer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioSampleBuffer::AudioSampleBuffer (const int numChannels_, const int numSamples) noexcept : numChannels (numChannels_), size (numSamples) { jassert (numSamples >= 0); jassert (numChannels_ > 0); allocateData(); } AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept : numChannels (other.numChannels), size (other.size) { allocateData(); const size_t numBytes = size * sizeof (float); for (int i = 0; i < numChannels; ++i) memcpy (channels[i], other.channels[i], numBytes); } void AudioSampleBuffer::allocateData() { const size_t channelListSize = (numChannels + 1) * sizeof (float*); allocatedBytes = (int) (numChannels * size * sizeof (float) + channelListSize + 32); allocatedData.malloc (allocatedBytes); channels = reinterpret_cast (allocatedData.getData()); float* chan = (float*) (allocatedData + channelListSize); for (int i = 0; i < numChannels; ++i) { channels[i] = chan; chan += size; } channels [numChannels] = 0; } AudioSampleBuffer::AudioSampleBuffer (float** dataToReferTo, const int numChannels_, const int numSamples) noexcept : numChannels (numChannels_), size (numSamples), allocatedBytes (0) { jassert (numChannels_ > 0); allocateChannels (dataToReferTo, 0); } AudioSampleBuffer::AudioSampleBuffer (float** dataToReferTo, const int numChannels_, const int startSample, const int numSamples) noexcept : numChannels (numChannels_), size (numSamples), allocatedBytes (0) { jassert (numChannels_ > 0); allocateChannels (dataToReferTo, startSample); } void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, const int newNumChannels, const int newNumSamples) noexcept { jassert (newNumChannels > 0); allocatedBytes = 0; allocatedData.free(); numChannels = newNumChannels; size = newNumSamples; allocateChannels (dataToReferTo, 0); } void AudioSampleBuffer::allocateChannels (float** const dataToReferTo, int offset) { // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) if (numChannels < numElementsInArray (preallocatedChannelSpace)) { channels = static_cast (preallocatedChannelSpace); } else { allocatedData.malloc (numChannels + 1, sizeof (float*)); channels = reinterpret_cast (allocatedData.getData()); } for (int i = 0; i < numChannels; ++i) { // you have to pass in the same number of valid pointers as numChannels jassert (dataToReferTo[i] != nullptr); channels[i] = dataToReferTo[i] + offset; } channels [numChannels] = 0; } AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept { if (this != &other) { setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); const size_t numBytes = size * sizeof (float); for (int i = 0; i < numChannels; ++i) memcpy (channels[i], other.channels[i], numBytes); } return *this; } AudioSampleBuffer::~AudioSampleBuffer() noexcept { } void AudioSampleBuffer::setSize (const int newNumChannels, const int newNumSamples, const bool keepExistingContent, const bool clearExtraSpace, const bool avoidReallocating) noexcept { jassert (newNumChannels > 0); if (newNumSamples != size || newNumChannels != numChannels) { const size_t channelListSize = (newNumChannels + 1) * sizeof (float*); const size_t newTotalBytes = (newNumChannels * newNumSamples * sizeof (float)) + channelListSize + 32; if (keepExistingContent) { HeapBlock newData; newData.allocate (newTotalBytes, clearExtraSpace); const int numChansToCopy = jmin (numChannels, newNumChannels); const size_t numBytesToCopy = sizeof (float) * jmin (newNumSamples, size); float** const newChannels = reinterpret_cast (newData.getData()); float* newChan = reinterpret_cast (newData + channelListSize); for (int i = 0; i < numChansToCopy; ++i) { memcpy (newChan, channels[i], numBytesToCopy); newChannels[i] = newChan; newChan += newNumSamples; } allocatedData.swapWith (newData); allocatedBytes = (int) newTotalBytes; channels = newChannels; } else { if (avoidReallocating && allocatedBytes >= newTotalBytes) { if (clearExtraSpace) allocatedData.clear (newTotalBytes); } else { allocatedBytes = newTotalBytes; allocatedData.allocate (newTotalBytes, clearExtraSpace); channels = reinterpret_cast (allocatedData.getData()); } float* chan = reinterpret_cast (allocatedData + channelListSize); for (int i = 0; i < newNumChannels; ++i) { channels[i] = chan; chan += newNumSamples; } } channels [newNumChannels] = 0; size = newNumSamples; numChannels = newNumChannels; } } void AudioSampleBuffer::clear() noexcept { for (int i = 0; i < numChannels; ++i) zeromem (channels[i], size * sizeof (float)); } void AudioSampleBuffer::clear (const int startSample, const int numSamples) noexcept { jassert (startSample >= 0 && startSample + numSamples <= size); for (int i = 0; i < numChannels; ++i) zeromem (channels [i] + startSample, numSamples * sizeof (float)); } void AudioSampleBuffer::clear (const int channel, const int startSample, const int numSamples) noexcept { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); zeromem (channels [channel] + startSample, numSamples * sizeof (float)); } void AudioSampleBuffer::applyGain (const int channel, const int startSample, int numSamples, const float gain) noexcept { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (gain != 1.0f) { float* d = channels [channel] + startSample; if (gain == 0.0f) { zeromem (d, sizeof (float) * numSamples); } else { while (--numSamples >= 0) *d++ *= gain; } } } void AudioSampleBuffer::applyGainRamp (const int channel, const int startSample, int numSamples, float startGain, float endGain) noexcept { if (startGain == endGain) { applyGain (channel, startSample, numSamples, startGain); } else { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); const float increment = (endGain - startGain) / numSamples; float* d = channels [channel] + startSample; while (--numSamples >= 0) { *d++ *= startGain; startGain += increment; } } } void AudioSampleBuffer::applyGain (const int startSample, const int numSamples, const float gain) noexcept { for (int i = 0; i < numChannels; ++i) applyGain (i, startSample, numSamples, gain); } void AudioSampleBuffer::addFrom (const int destChannel, const int destStartSample, const AudioSampleBuffer& source, const int sourceChannel, const int sourceStartSample, int numSamples, const float gain) noexcept { jassert (&source != this || sourceChannel != destChannel); jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (gain != 0.0f && numSamples > 0) { float* d = channels [destChannel] + destStartSample; const float* s = source.channels [sourceChannel] + sourceStartSample; if (gain != 1.0f) { while (--numSamples >= 0) *d++ += gain * *s++; } else { while (--numSamples >= 0) *d++ += *s++; } } } void AudioSampleBuffer::addFrom (const int destChannel, const int destStartSample, const float* source, int numSamples, const float gain) noexcept { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); if (gain != 0.0f && numSamples > 0) { float* d = channels [destChannel] + destStartSample; if (gain != 1.0f) { while (--numSamples >= 0) *d++ += gain * *source++; } else { while (--numSamples >= 0) *d++ += *source++; } } } void AudioSampleBuffer::addFromWithRamp (const int destChannel, const int destStartSample, const float* source, int numSamples, float startGain, const float endGain) noexcept { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); if (startGain == endGain) { addFrom (destChannel, destStartSample, source, numSamples, startGain); } else { if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) { const float increment = (endGain - startGain) / numSamples; float* d = channels [destChannel] + destStartSample; while (--numSamples >= 0) { *d++ += startGain * *source++; startGain += increment; } } } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const AudioSampleBuffer& source, const int sourceChannel, const int sourceStartSample, int numSamples) noexcept { jassert (&source != this || sourceChannel != destChannel); jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (numSamples > 0) { memcpy (channels [destChannel] + destStartSample, source.channels [sourceChannel] + sourceStartSample, sizeof (float) * numSamples); } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const float* source, int numSamples) noexcept { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); if (numSamples > 0) { memcpy (channels [destChannel] + destStartSample, source, sizeof (float) * numSamples); } } void AudioSampleBuffer::copyFrom (const int destChannel, const int destStartSample, const float* source, int numSamples, const float gain) noexcept { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); if (numSamples > 0) { float* d = channels [destChannel] + destStartSample; if (gain != 1.0f) { if (gain == 0) { zeromem (d, sizeof (float) * numSamples); } else { while (--numSamples >= 0) *d++ = gain * *source++; } } else { memcpy (d, source, sizeof (float) * numSamples); } } } void AudioSampleBuffer::copyFromWithRamp (const int destChannel, const int destStartSample, const float* source, int numSamples, float startGain, float endGain) noexcept { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); if (startGain == endGain) { copyFrom (destChannel, destStartSample, source, numSamples, startGain); } else { if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) { const float increment = (endGain - startGain) / numSamples; float* d = channels [destChannel] + destStartSample; while (--numSamples >= 0) { *d++ = startGain * *source++; startGain += increment; } } } } void AudioSampleBuffer::findMinMax (const int channel, const int startSample, int numSamples, float& minVal, float& maxVal) const noexcept { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); findMinAndMax (channels [channel] + startSample, numSamples, minVal, maxVal); } float AudioSampleBuffer::getMagnitude (const int channel, const int startSample, const int numSamples) const noexcept { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); float mn, mx; findMinMax (channel, startSample, numSamples, mn, mx); return jmax (mn, -mn, mx, -mx); } float AudioSampleBuffer::getMagnitude (const int startSample, const int numSamples) const noexcept { float mag = 0.0f; for (int i = 0; i < numChannels; ++i) mag = jmax (mag, getMagnitude (i, startSample, numSamples)); return mag; } float AudioSampleBuffer::getRMSLevel (const int channel, const int startSample, const int numSamples) const noexcept { jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples <= 0 || channel < 0 || channel >= numChannels) return 0.0f; const float* const data = channels [channel] + startSample; double sum = 0.0; for (int i = 0; i < numSamples; ++i) { const float sample = data [i]; sum += sample * sample; } return (float) std::sqrt (sum / numSamples); } void AudioSampleBuffer::readFromAudioReader (AudioFormatReader* reader, const int startSample, const int numSamples, const int64 readerStartSample, const bool useLeftChan, const bool useRightChan) { jassert (reader != nullptr); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples > 0) { int* chans[3]; if (useLeftChan == useRightChan) { chans[0] = reinterpret_cast (getSampleData (0, startSample)); chans[1] = (reader->numChannels > 1 && getNumChannels() > 1) ? reinterpret_cast (getSampleData (1, startSample)) : 0; } else if (useLeftChan || (reader->numChannels == 1)) { chans[0] = reinterpret_cast (getSampleData (0, startSample)); chans[1] = nullptr; } else if (useRightChan) { chans[0] = nullptr; chans[1] = reinterpret_cast (getSampleData (0, startSample)); } chans[2] = nullptr; reader->read (chans, 2, readerStartSample, numSamples, true); if (! reader->usesFloatingPointData) { for (int j = 0; j < 2; ++j) { float* const d = reinterpret_cast (chans[j]); if (d != nullptr) { const float multiplier = 1.0f / 0x7fffffff; for (int i = 0; i < numSamples; ++i) d[i] = *reinterpret_cast (d + i) * multiplier; } } } if (numChannels > 1 && (chans[0] == nullptr || chans[1] == nullptr)) { // if this is a stereo buffer and the source was mono, dupe the first channel.. memcpy (getSampleData (1, startSample), getSampleData (0, startSample), sizeof (float) * numSamples); } } } void AudioSampleBuffer::writeToAudioWriter (AudioFormatWriter* writer, const int startSample, const int numSamples) const { jassert (writer != nullptr); writer->writeFromAudioSampleBuffer (*this, startSample, numSamples); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioSampleBuffer.cpp ***/ /*** Start of inlined file: juce_IIRFilter.cpp ***/ BEGIN_JUCE_NAMESPACE IIRFilter::IIRFilter() : active (false) { reset(); } IIRFilter::IIRFilter (const IIRFilter& other) : active (other.active) { const ScopedLock sl (other.processLock); memcpy (coefficients, other.coefficients, sizeof (coefficients)); reset(); } IIRFilter::~IIRFilter() { } void IIRFilter::reset() noexcept { const ScopedLock sl (processLock); x1 = 0; x2 = 0; y1 = 0; y2 = 0; } float IIRFilter::processSingleSampleRaw (const float in) noexcept { float out = coefficients[0] * in + coefficients[1] * x1 + coefficients[2] * x2 - coefficients[4] * y1 - coefficients[5] * y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif x2 = x1; x1 = in; y2 = y1; y1 = out; return out; } void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept { const ScopedLock sl (processLock); if (active) { for (int i = 0; i < numSamples; ++i) { const float in = samples[i]; float out = coefficients[0] * in + coefficients[1] * x1 + coefficients[2] * x2 - coefficients[4] * y1 - coefficients[5] * y2; #if JUCE_INTEL if (! (out < -1.0e-8 || out > 1.0e-8)) out = 0; #endif x2 = x1; x1 = in; y2 = y1; y1 = out; samples[i] = out; } } } void IIRFilter::makeLowPass (const double sampleRate, const double frequency) noexcept { jassert (sampleRate > 0); const double n = 1.0 / tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setCoefficients (c1, c1 * 2.0f, c1, 1.0, c1 * 2.0 * (1.0 - nSquared), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void IIRFilter::makeHighPass (const double sampleRate, const double frequency) noexcept { const double n = tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); setCoefficients (c1, c1 * -2.0f, c1, 1.0, c1 * 2.0 * (nSquared - 1.0), c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); } void IIRFilter::makeLowShelf (const double sampleRate, const double cutOffFrequency, const double Q, const float gainFactor) noexcept { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double aminus1 = A - 1.0; const double aplus1 = A + 1.0; const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; const double coso = std::cos (omega); const double beta = std::sin (omega) * std::sqrt (A) / Q; const double aminus1TimesCoso = aminus1 * coso; setCoefficients (A * (aplus1 - aminus1TimesCoso + beta), A * 2.0 * (aminus1 - aplus1 * coso), A * (aplus1 - aminus1TimesCoso - beta), aplus1 + aminus1TimesCoso + beta, -2.0 * (aminus1 + aplus1 * coso), aplus1 + aminus1TimesCoso - beta); } void IIRFilter::makeHighShelf (const double sampleRate, const double cutOffFrequency, const double Q, const float gainFactor) noexcept { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double aminus1 = A - 1.0; const double aplus1 = A + 1.0; const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; const double coso = std::cos (omega); const double beta = std::sin (omega) * std::sqrt (A) / Q; const double aminus1TimesCoso = aminus1 * coso; setCoefficients (A * (aplus1 + aminus1TimesCoso + beta), A * -2.0 * (aminus1 + aplus1 * coso), A * (aplus1 + aminus1TimesCoso - beta), aplus1 - aminus1TimesCoso + beta, 2.0 * (aminus1 - aplus1 * coso), aplus1 - aminus1TimesCoso - beta); } void IIRFilter::makeBandPass (const double sampleRate, const double centreFrequency, const double Q, const float gainFactor) noexcept { jassert (sampleRate > 0); jassert (Q > 0); const double A = jmax (0.0f, gainFactor); const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; const double alpha = 0.5 * std::sin (omega) / Q; const double c2 = -2.0 * std::cos (omega); const double alphaTimesA = alpha * A; const double alphaOverA = alpha / A; setCoefficients (1.0 + alphaTimesA, c2, 1.0 - alphaTimesA, 1.0 + alphaOverA, c2, 1.0 - alphaOverA); } void IIRFilter::makeInactive() noexcept { const ScopedLock sl (processLock); active = false; } void IIRFilter::copyCoefficientsFrom (const IIRFilter& other) noexcept { const ScopedLock sl (processLock); memcpy (coefficients, other.coefficients, sizeof (coefficients)); active = other.active; } void IIRFilter::setCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) noexcept { const double a = 1.0 / c4; c1 *= a; c2 *= a; c3 *= a; c5 *= a; c6 *= a; const ScopedLock sl (processLock); coefficients[0] = (float) c1; coefficients[1] = (float) c2; coefficients[2] = (float) c3; coefficients[3] = (float) c4; coefficients[4] = (float) c5; coefficients[5] = (float) c6; active = true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_IIRFilter.cpp ***/ /*** Start of inlined file: juce_MidiOutput.cpp ***/ BEGIN_JUCE_NAMESPACE MidiOutput::MidiOutput() : Thread ("midi out"), internal (nullptr), firstMessage (nullptr) { } MidiOutput::PendingMessage::PendingMessage (const void* const data, const int len, const double timeStamp) : message (data, len, timeStamp) { } void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, const double millisecondCounterToStartAt, double samplesPerSecondForBuffer) { // You've got to call startBackgroundThread() for this to actually work.. jassert (isThreadRunning()); // this needs to be a value in the future - RTFM for this method! jassert (millisecondCounterToStartAt > 0); const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; MidiBuffer::Iterator i (buffer); const uint8* data; int len, time; while (i.getNextEvent (data, len, time)) { const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; PendingMessage* const m = new PendingMessage (data, len, eventTime); const ScopedLock sl (lock); if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime) { m->next = firstMessage; firstMessage = m; } else { PendingMessage* mm = firstMessage; while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime) mm = mm->next; m->next = mm->next; mm->next = m; } } notify(); } void MidiOutput::clearAllPendingMessages() { const ScopedLock sl (lock); while (firstMessage != nullptr) { PendingMessage* const m = firstMessage; firstMessage = firstMessage->next; delete m; } } void MidiOutput::startBackgroundThread() { startThread (9); } void MidiOutput::stopBackgroundThread() { stopThread (5000); } void MidiOutput::run() { while (! threadShouldExit()) { uint32 now = Time::getMillisecondCounter(); uint32 eventTime = 0; uint32 timeToWait = 500; PendingMessage* message; { const ScopedLock sl (lock); message = firstMessage; if (message != nullptr) { eventTime = roundToInt (message->message.getTimeStamp()); if (eventTime > now + 20) { timeToWait = eventTime - (now + 20); message = nullptr; } else { firstMessage = message->next; } } } if (message != nullptr) { if (eventTime > now) { Time::waitForMillisecondCounter (eventTime); if (threadShouldExit()) break; } if (eventTime > now - 200) sendMessageNow (message->message); delete message; } else { jassert (timeToWait < 1000 * 30); wait (timeToWait); } } clearAllPendingMessages(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiOutput.cpp ***/ /*** Start of inlined file: juce_MidiBuffer.cpp ***/ BEGIN_JUCE_NAMESPACE MidiBuffer::MidiBuffer() noexcept : bytesUsed (0) { } MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept : bytesUsed (0) { addEvent (message, 0); } MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data), bytesUsed (other.bytesUsed) { } MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept { bytesUsed = other.bytesUsed; data = other.data; return *this; } void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); std::swap (bytesUsed, other.bytesUsed); } MidiBuffer::~MidiBuffer() { } inline uint8* MidiBuffer::getData() const noexcept { return static_cast (data.getData()); } inline int MidiBuffer::getEventTime (const void* const d) noexcept { return *static_cast (d); } inline uint16 MidiBuffer::getEventDataSize (const void* const d) noexcept { return *reinterpret_cast (static_cast (d) + sizeof (int)); } inline uint16 MidiBuffer::getEventTotalSize (const void* const d) noexcept { return getEventDataSize (d) + sizeof (int) + sizeof (uint16); } void MidiBuffer::clear() noexcept { bytesUsed = 0; } void MidiBuffer::clear (const int startSample, const int numSamples) { uint8* const start = findEventAfter (getData(), startSample - 1); uint8* const end = findEventAfter (start, startSample + numSamples - 1); if (end > start) { const int bytesToMove = bytesUsed - (int) (end - getData()); if (bytesToMove > 0) memmove (start, end, bytesToMove); bytesUsed -= (int) (end - start); } } void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) { addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); } namespace MidiBufferHelpers { int findActualEventLength (const uint8* const data, const int maxBytes) noexcept { unsigned int byte = (unsigned int) *data; int size = 0; if (byte == 0xf0 || byte == 0xf7) { const uint8* d = data + 1; while (d < data + maxBytes) if (*d++ == 0xf7) break; size = (int) (d - data); } else if (byte == 0xff) { int n; const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); size = jmin (maxBytes, n + 2 + bytesLeft); } else if (byte >= 0x80) { size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); } return size; } } void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) { const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); if (numBytes > 0) { int spaceNeeded = bytesUsed + numBytes + sizeof (int) + sizeof (uint16); data.ensureSize ((spaceNeeded + spaceNeeded / 2 + 8) & ~7); uint8* d = findEventAfter (getData(), sampleNumber); const int bytesToMove = bytesUsed - (int) (d - getData()); if (bytesToMove > 0) memmove (d + numBytes + sizeof (int) + sizeof (uint16), d, bytesToMove); *reinterpret_cast (d) = sampleNumber; d += sizeof (int); *reinterpret_cast (d) = (uint16) numBytes; d += sizeof (uint16); memcpy (d, newData, numBytes); bytesUsed += numBytes + sizeof (int) + sizeof (uint16); } } void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, const int startSample, const int numSamples, const int sampleDeltaToAdd) { Iterator i (otherBuffer); i.setNextSamplePosition (startSample); const uint8* eventData; int eventSize, position; while (i.getNextEvent (eventData, eventSize, position) && (position < startSample + numSamples || numSamples < 0)) { addEvent (eventData, eventSize, position + sampleDeltaToAdd); } } void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureSize (minimumNumBytes); } bool MidiBuffer::isEmpty() const noexcept { return bytesUsed == 0; } int MidiBuffer::getNumEvents() const noexcept { int n = 0; const uint8* d = getData(); const uint8* const end = d + bytesUsed; while (d < end) { d += getEventTotalSize (d); ++n; } return n; } int MidiBuffer::getFirstEventTime() const noexcept { return bytesUsed > 0 ? getEventTime (data.getData()) : 0; } int MidiBuffer::getLastEventTime() const noexcept { if (bytesUsed == 0) return 0; const uint8* d = getData(); const uint8* const endData = d + bytesUsed; for (;;) { const uint8* const nextOne = d + getEventTotalSize (d); if (nextOne >= endData) return getEventTime (d); d = nextOne; } } uint8* MidiBuffer::findEventAfter (uint8* d, const int samplePosition) const noexcept { const uint8* const endData = getData() + bytesUsed; while (d < endData && getEventTime (d) <= samplePosition) d += getEventTotalSize (d); return d; } MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) noexcept : buffer (buffer_), data (buffer_.getData()) { } MidiBuffer::Iterator::~Iterator() noexcept { } void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept { data = buffer.getData(); const uint8* dataEnd = data + buffer.bytesUsed; while (data < dataEnd && getEventTime (data) < samplePosition) data += getEventTotalSize (data); } bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept { if (data >= buffer.getData() + buffer.bytesUsed) return false; samplePosition = getEventTime (data); numBytes = getEventDataSize (data); data += sizeof (int) + sizeof (uint16); midiData = data; data += numBytes; return true; } bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept { if (data >= buffer.getData() + buffer.bytesUsed) return false; samplePosition = getEventTime (data); const int numBytes = getEventDataSize (data); data += sizeof (int) + sizeof (uint16); result = MidiMessage (data, numBytes, samplePosition); data += numBytes; return true; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiBuffer.cpp ***/ /*** Start of inlined file: juce_MidiFile.cpp ***/ BEGIN_JUCE_NAMESPACE namespace MidiFileHelpers { void writeVariableLengthInt (OutputStream& out, unsigned int v) { unsigned int buffer = v & 0x7F; while ((v >>= 7) != 0) { buffer <<= 8; buffer |= ((v & 0x7F) | 0x80); } for (;;) { out.writeByte ((char) buffer); if (buffer & 0x80) buffer >>= 8; else break; } } bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept { unsigned int ch = (int) ByteOrder::bigEndianInt (data); data += 4; if (ch != ByteOrder::bigEndianInt ("MThd")) { bool ok = false; if (ch == ByteOrder::bigEndianInt ("RIFF")) { for (int i = 0; i < 8; ++i) { ch = ByteOrder::bigEndianInt (data); data += 4; if (ch == ByteOrder::bigEndianInt ("MThd")) { ok = true; break; } } } if (! ok) return false; } unsigned int bytesRemaining = ByteOrder::bigEndianInt (data); data += 4; fileType = (short) ByteOrder::bigEndianShort (data); data += 2; numberOfTracks = (short) ByteOrder::bigEndianShort (data); data += 2; timeFormat = (short) ByteOrder::bigEndianShort (data); data += 2; bytesRemaining -= 6; data += bytesRemaining; return true; } double convertTicksToSeconds (const double time, const MidiMessageSequence& tempoEvents, const int timeFormat) { if (timeFormat > 0) { int numer = 4, denom = 4; double tempoTime = 0.0, correctedTempoTime = 0.0; const double tickLen = 1.0 / (timeFormat & 0x7fff); double secsPerTick = 0.5 * tickLen; const int numEvents = tempoEvents.getNumEvents(); for (int i = 0; i < numEvents; ++i) { const MidiMessage& m = tempoEvents.getEventPointer(i)->message; if (time <= m.getTimeStamp()) break; if (timeFormat > 0) { correctedTempoTime = correctedTempoTime + (m.getTimeStamp() - tempoTime) * secsPerTick; } else { correctedTempoTime = tickLen * m.getTimeStamp() / (((timeFormat & 0x7fff) >> 8) * (timeFormat & 0xff)); } tempoTime = m.getTimeStamp(); if (m.isTempoMetaEvent()) secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); else if (m.isTimeSignatureMetaEvent()) m.getTimeSignatureInfo (numer, denom); while (i + 1 < numEvents) { const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message; if (m2.getTimeStamp() == tempoTime) { ++i; if (m2.isTempoMetaEvent()) secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); else if (m2.isTimeSignatureMetaEvent()) m2.getTimeSignatureInfo (numer, denom); } else { break; } } } return correctedTempoTime + (time - tempoTime) * secsPerTick; } else { return time / (((timeFormat & 0x7fff) >> 8) * (timeFormat & 0xff)); } } // a comparator that puts all the note-offs before note-ons that have the same time struct Sorter { static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, const MidiMessageSequence::MidiEventHolder* const second) noexcept { const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); if (diff > 0) return 1; if (diff < 0) return -1; if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; return 0; } }; } MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) { } MidiFile::~MidiFile() { clear(); } void MidiFile::clear() { tracks.clear(); } int MidiFile::getNumTracks() const noexcept { return tracks.size(); } const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept { return tracks [index]; } void MidiFile::addTrack (const MidiMessageSequence& trackSequence) { tracks.add (new MidiMessageSequence (trackSequence)); } short MidiFile::getTimeFormat() const noexcept { return timeFormat; } void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept { timeFormat = (short) ticks; } void MidiFile::setSmpteTimeFormat (const int framesPerSecond, const int subframeResolution) noexcept { timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); } void MidiFile::findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const { for (int i = tracks.size(); --i >= 0;) { const int numEvents = tracks.getUnchecked(i)->getNumEvents(); for (int j = 0; j < numEvents; ++j) { const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; if (m.isTempoMetaEvent()) tempoChangeEvents.addEvent (m); } } } void MidiFile::findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const { for (int i = tracks.size(); --i >= 0;) { const int numEvents = tracks.getUnchecked(i)->getNumEvents(); for (int j = 0; j < numEvents; ++j) { const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; if (m.isTimeSignatureMetaEvent()) timeSigEvents.addEvent (m); } } } double MidiFile::getLastTimestamp() const { double t = 0.0; for (int i = tracks.size(); --i >= 0;) t = jmax (t, tracks.getUnchecked(i)->getEndTime()); return t; } bool MidiFile::readFrom (InputStream& sourceStream) { clear(); MemoryBlock data; const int maxSensibleMidiFileSize = 2 * 1024 * 1024; // (put a sanity-check on the file size, as midi files are generally small) if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) { size_t size = data.getSize(); const uint8* d = static_cast (data.getData()); short fileType, expectedTracks; if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) { size -= (int) (d - static_cast (data.getData())); int track = 0; while (size > 0 && track < expectedTracks) { const int chunkType = (int) ByteOrder::bigEndianInt (d); d += 4; const int chunkSize = (int) ByteOrder::bigEndianInt (d); d += 4; if (chunkSize <= 0) break; if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) { readNextTrack (d, chunkSize); } size -= chunkSize + 8; d += chunkSize; ++track; } return true; } } return false; } void MidiFile::readNextTrack (const uint8* data, int size) { double time = 0; char lastStatusByte = 0; MidiMessageSequence result; while (size > 0) { int bytesUsed; const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed); data += bytesUsed; size -= bytesUsed; time += delay; int messSize = 0; const MidiMessage mm (data, size, messSize, lastStatusByte, time); if (messSize <= 0) break; size -= messSize; data += messSize; result.addEvent (mm); const char firstByte = *(mm.getRawData()); if ((firstByte & 0xf0) != 0xf0) lastStatusByte = firstByte; } // use a sort that puts all the note-offs before note-ons that have the same time MidiFileHelpers::Sorter sorter; result.list.sort (sorter, true); result.updateMatchedPairs(); addTrack (result); } void MidiFile::convertTimestampTicksToSeconds() { MidiMessageSequence tempoEvents; findAllTempoEvents (tempoEvents); findAllTimeSigEvents (tempoEvents); for (int i = 0; i < tracks.size(); ++i) { MidiMessageSequence& ms = *tracks.getUnchecked(i); for (int j = ms.getNumEvents(); --j >= 0;) { MidiMessage& m = ms.getEventPointer(j)->message; m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); } } } bool MidiFile::writeTo (OutputStream& out) { out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); out.writeIntBigEndian (6); out.writeShortBigEndian (1); // type out.writeShortBigEndian ((short) tracks.size()); out.writeShortBigEndian (timeFormat); for (int i = 0; i < tracks.size(); ++i) writeTrack (out, i); out.flush(); return true; } void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum) { MemoryOutputStream out; const MidiMessageSequence& ms = *tracks[trackNum]; int lastTick = 0; char lastStatusByte = 0; for (int i = 0; i < ms.getNumEvents(); ++i) { const MidiMessage& mm = ms.getEventPointer(i)->message; const int tick = roundToInt (mm.getTimeStamp()); const int delta = jmax (0, tick - lastTick); MidiFileHelpers::writeVariableLengthInt (out, delta); lastTick = tick; const char statusByte = *(mm.getRawData()); if ((statusByte == lastStatusByte) && ((statusByte & 0xf0) != 0xf0) && i > 0 && mm.getRawDataSize() > 1) { out.write (mm.getRawData() + 1, mm.getRawDataSize() - 1); } else { out.write (mm.getRawData(), mm.getRawDataSize()); } lastStatusByte = statusByte; } out.writeByte (0); const MidiMessage m (MidiMessage::endOfTrack()); out.write (m.getRawData(), m.getRawDataSize()); mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk")); mainOut.writeIntBigEndian ((int) out.getDataSize()); mainOut << out; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiFile.cpp ***/ /*** Start of inlined file: juce_MidiKeyboardState.cpp ***/ BEGIN_JUCE_NAMESPACE MidiKeyboardState::MidiKeyboardState() { zerostruct (noteStates); } MidiKeyboardState::~MidiKeyboardState() { } void MidiKeyboardState::reset() { const ScopedLock sl (lock); zerostruct (noteStates); eventsToAdd.clear(); } bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept { jassert (midiChannel >= 0 && midiChannel <= 16); return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & (1 << (midiChannel - 1))) != 0; } bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept { return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & midiChannelMask) != 0; } void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { jassert (midiChannel >= 0 && midiChannel <= 16); jassert (isPositiveAndBelow (midiNoteNumber, (int) 128)); const ScopedLock sl (lock); if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); eventsToAdd.clear (0, timeNow - 500); noteOnInternal (midiChannel, midiNoteNumber, velocity); } } void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) { if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); for (int i = listeners.size(); --i >= 0;) listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity); } } void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber) { const ScopedLock sl (lock); if (isNoteOn (midiChannel, midiNoteNumber)) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); eventsToAdd.clear (0, timeNow - 500); noteOffInternal (midiChannel, midiNoteNumber); } } void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber) { if (isNoteOn (midiChannel, midiNoteNumber)) { noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); for (int i = listeners.size(); --i >= 0;) listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber); } } void MidiKeyboardState::allNotesOff (const int midiChannel) { const ScopedLock sl (lock); if (midiChannel <= 0) { for (int i = 1; i <= 16; ++i) allNotesOff (i); } else { for (int i = 0; i < 128; ++i) noteOff (midiChannel, i); } } void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) { if (message.isNoteOn()) { noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); } else if (message.isNoteOff()) { noteOffInternal (message.getChannel(), message.getNoteNumber()); } else if (message.isAllNotesOff()) { for (int i = 0; i < 128; ++i) noteOffInternal (message.getChannel(), i); } } void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, const int startSample, const int numSamples, const bool injectIndirectEvents) { MidiBuffer::Iterator i (buffer); MidiMessage message (0xf4, 0.0); int time; const ScopedLock sl (lock); while (i.getNextEvent (message, time)) processNextMidiEvent (message); if (injectIndirectEvents) { MidiBuffer::Iterator i2 (eventsToAdd); const int firstEventToAdd = eventsToAdd.getFirstEventTime(); const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); while (i2.getNextEvent (message, time)) { const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor)); buffer.addEvent (message, startSample + pos); } } eventsToAdd.clear(); } void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener) { const ScopedLock sl (lock); listeners.addIfNotAlreadyThere (listener); } void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener) { const ScopedLock sl (lock); listeners.removeValue (listener); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiKeyboardState.cpp ***/ /*** Start of inlined file: juce_MidiMessage.cpp ***/ BEGIN_JUCE_NAMESPACE namespace MidiHelpers { inline uint8 initialByte (const int type, const int channel) noexcept { return (uint8) (type | jlimit (0, 15, channel - 1)); } inline uint8 validVelocity (const int v) noexcept { return (uint8) jlimit (0, 127, v); } } int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept { numBytesUsed = 0; int v = 0; int i; do { i = (int) *data++; if (++numBytesUsed > 6) break; v = (v << 7) + (i & 0x7f); } while (i & 0x80); return v; } int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept { // this method only works for valid starting bytes of a short midi message jassert (firstByte >= 0x80 && firstByte != 0xf0 && firstByte != 0xf7); static const char messageLengths[] = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; return messageLengths [firstByte & 0x7f]; } MidiMessage::MidiMessage() noexcept : timeStamp (0), data (static_cast (preallocatedData.asBytes)), size (2) { data[0] = 0xf0; data[1] = 0xf7; } MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) : timeStamp (t), size (dataSize) { jassert (dataSize > 0); if (dataSize <= 4) data = static_cast (preallocatedData.asBytes); else data = new uint8 [dataSize]; memcpy (data, d, dataSize); // check that the length matches the data.. jassert (size > 3 || data[0] >= 0xf0 || getMessageLengthFromFirstByte (data[0]) == size); } MidiMessage::MidiMessage (const int byte1, const double t) noexcept : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (1) { data[0] = (uint8) byte1; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); } MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (2) { data[0] = (uint8) byte1; data[1] = (uint8) byte2; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); } MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept : timeStamp (t), data (static_cast (preallocatedData.asBytes)), size (3) { data[0] = (uint8) byte1; data[1] = (uint8) byte2; data[2] = (uint8) byte3; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); } MidiMessage::MidiMessage (const MidiMessage& other) : timeStamp (other.timeStamp), size (other.size) { if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) : timeStamp (newTimeStamp), size (other.size) { if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } MidiMessage::MidiMessage (const void* src_, int sz, int& numBytesUsed, const uint8 lastStatusByte, double t) : timeStamp (t), data (static_cast (preallocatedData.asBytes)) { const uint8* src = static_cast (src_); unsigned int byte = (unsigned int) *src; if (byte < 0x80) { byte = (unsigned int) (uint8) lastStatusByte; numBytesUsed = -1; } else { numBytesUsed = 0; --sz; ++src; } if (byte >= 0x80) { if (byte == 0xf0) { const uint8* d = src; bool haveReadAllLengthBytes = false; while (d < src + sz) { if (*d >= 0x80) { if (*d == 0xf7) { ++d; // include the trailing 0xf7 when we hit it break; } if (haveReadAllLengthBytes) // if we see a 0x80 bit set after the initial data length break; // bytes, assume it's the end of the sysex ++d; continue; } haveReadAllLengthBytes = true; ++d; } size = 1 + (int) (d - src); data = new uint8 [size]; *data = (uint8) byte; memcpy (data + 1, src, size - 1); } else if (byte == 0xff) { int n; const int bytesLeft = readVariableLengthVal (src + 1, n); size = jmin (sz + 1, n + 2 + bytesLeft); data = new uint8 [size]; *data = (uint8) byte; memcpy (data + 1, src, size - 1); } else { preallocatedData.asInt32 = 0; size = getMessageLengthFromFirstByte ((uint8) byte); data[0] = (uint8) byte; if (size > 1) { data[1] = src[0]; if (size > 2) data[2] = src[1]; } } numBytesUsed += size; } else { preallocatedData.asInt32 = 0; size = 0; } } MidiMessage& MidiMessage::operator= (const MidiMessage& other) { if (this != &other) { timeStamp = other.timeStamp; size = other.size; if (data != static_cast (preallocatedData.asBytes)) delete[] data; if (other.data != static_cast (other.preallocatedData.asBytes)) { data = new uint8 [size]; memcpy (data, other.data, size); } else { data = static_cast (preallocatedData.asBytes); preallocatedData.asInt32 = other.preallocatedData.asInt32; } } return *this; } MidiMessage::~MidiMessage() { if (data != static_cast (preallocatedData.asBytes)) delete[] data; } int MidiMessage::getChannel() const noexcept { if ((data[0] & 0xf0) != 0xf0) return (data[0] & 0xf) + 1; return 0; } bool MidiMessage::isForChannel (const int channel) const noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 return ((data[0] & 0xf) == channel - 1) && ((data[0] & 0xf0) != 0xf0); } void MidiMessage::setChannel (const int channel) noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 if ((data[0] & 0xf0) != (uint8) 0xf0) data[0] = (uint8) ((data[0] & (uint8) 0xf0) | (uint8)(channel - 1)); } bool MidiMessage::isNoteOn (const bool returnTrueForVelocity0) const noexcept { return ((data[0] & 0xf0) == 0x90) && (returnTrueForVelocity0 || data[2] != 0); } bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const noexcept { return ((data[0] & 0xf0) == 0x80) || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); } bool MidiMessage::isNoteOnOrOff() const noexcept { const int d = data[0] & 0xf0; return (d == 0x90) || (d == 0x80); } int MidiMessage::getNoteNumber() const noexcept { return data[1]; } void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept { if (isNoteOnOrOff()) data[1] = (char) (newNoteNumber & 127); } uint8 MidiMessage::getVelocity() const noexcept { if (isNoteOnOrOff()) return data[2]; return 0; } float MidiMessage::getFloatVelocity() const noexcept { return getVelocity() * (1.0f / 127.0f); } void MidiMessage::setVelocity (const float newVelocity) noexcept { if (isNoteOnOrOff()) data[2] = MidiHelpers::validVelocity (roundToInt (newVelocity * 127.0f)); } void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept { if (isNoteOnOrOff()) data[2] = MidiHelpers::validVelocity (roundToInt (scaleFactor * data[2])); } bool MidiMessage::isAftertouch() const noexcept { return (data[0] & 0xf0) == 0xa0; } int MidiMessage::getAfterTouchValue() const noexcept { jassert (isAftertouch()); return data[2]; } MidiMessage MidiMessage::aftertouchChange (const int channel, const int noteNum, const int aftertouchValue) noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (noteNum, (int) 128)); jassert (isPositiveAndBelow (aftertouchValue, (int) 128)); return MidiMessage (MidiHelpers::initialByte (0xa0, channel), noteNum & 0x7f, aftertouchValue & 0x7f); } bool MidiMessage::isChannelPressure() const noexcept { return (data[0] & 0xf0) == 0xd0; } int MidiMessage::getChannelPressureValue() const noexcept { jassert (isChannelPressure()); return data[1]; } MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (pressure, (int) 128)); return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); } bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && data[2] >= 64; } bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && data[2] < 64; } bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && data[2] >= 64; } bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && data[2] < 64; } bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && data[2] >= 64; } bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && data[2] < 64; } bool MidiMessage::isProgramChange() const noexcept { return (data[0] & 0xf0) == 0xc0; } int MidiMessage::getProgramChangeNumber() const noexcept { jassert (isProgramChange()); return data[1]; } MidiMessage MidiMessage::programChange (const int channel, const int programNumber) noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 return MidiMessage (MidiHelpers::initialByte (0xc0, channel), programNumber & 0x7f); } bool MidiMessage::isPitchWheel() const noexcept { return (data[0] & 0xf0) == 0xe0; } int MidiMessage::getPitchWheelValue() const noexcept { jassert (isPitchWheel()); return data[1] | (data[2] << 7); } MidiMessage MidiMessage::pitchWheel (const int channel, const int position) noexcept { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (position, (int) 0x4000)); return MidiMessage (MidiHelpers::initialByte (0xe0, channel), position & 127, (position >> 7) & 127); } bool MidiMessage::isController() const noexcept { return (data[0] & 0xf0) == 0xb0; } bool MidiMessage::isControllerOfType (const int controllerType) const noexcept { return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; } int MidiMessage::getControllerNumber() const noexcept { jassert (isController()); return data[1]; } int MidiMessage::getControllerValue() const noexcept { jassert (isController()); return data[2]; } MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) noexcept { // the channel must be between 1 and 16 inclusive jassert (channel > 0 && channel <= 16); return MidiMessage (MidiHelpers::initialByte (0xb0, channel), controllerType & 127, value & 127); } MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept { return noteOn (channel, noteNumber, (uint8) (velocity * 127.0f)); } MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) noexcept { jassert (channel > 0 && channel <= 16); jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (MidiHelpers::initialByte (0x90, channel), noteNumber & 127, MidiHelpers::validVelocity (velocity)); } MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept { jassert (channel > 0 && channel <= 16); jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (MidiHelpers::initialByte (0x80, channel), noteNumber & 127, MidiHelpers::validVelocity (velocity)); } MidiMessage MidiMessage::allNotesOff (const int channel) noexcept { return controllerEvent (channel, 123, 0); } bool MidiMessage::isAllNotesOff() const noexcept { return (data[0] & 0xf0) == 0xb0 && data[1] == 123; } MidiMessage MidiMessage::allSoundOff (const int channel) noexcept { return controllerEvent (channel, 120, 0); } bool MidiMessage::isAllSoundOff() const noexcept { return (data[0] & 0xf0) == 0xb0 && data[1] == 120; } MidiMessage MidiMessage::allControllersOff (const int channel) noexcept { return controllerEvent (channel, 121, 0); } MidiMessage MidiMessage::masterVolume (const float volume) { const int vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); const uint8 buf[] = { 0xf0, 0x7f, 0x7f, 0x04, 0x01, (uint8) (vol & 0x7f), (uint8) (vol >> 7), 0xf7 }; return MidiMessage (buf, 8); } bool MidiMessage::isSysEx() const noexcept { return *data == 0xf0; } MidiMessage MidiMessage::createSysExMessage (const uint8* sysexData, const int dataSize) { HeapBlock m (dataSize + 2); m[0] = 0xf0; memcpy (m + 1, sysexData, dataSize); m[dataSize + 1] = 0xf7; return MidiMessage (m, dataSize + 2); } const uint8* MidiMessage::getSysExData() const noexcept { return isSysEx() ? getRawData() + 1 : nullptr; } int MidiMessage::getSysExDataSize() const noexcept { return isSysEx() ? size - 2 : 0; } bool MidiMessage::isMetaEvent() const noexcept { return *data == 0xff; } bool MidiMessage::isActiveSense() const noexcept { return *data == 0xfe; } int MidiMessage::getMetaEventType() const noexcept { return *data != 0xff ? -1 : data[1]; } int MidiMessage::getMetaEventLength() const noexcept { if (*data == 0xff) { int n; return jmin (size - 2, readVariableLengthVal (data + 2, n)); } return 0; } const uint8* MidiMessage::getMetaEventData() const noexcept { jassert (isMetaEvent()); int n; const uint8* d = data + 2; readVariableLengthVal (d, n); return d + n; } bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } bool MidiMessage::isEndOfTrackMetaEvent() const noexcept { return getMetaEventType() == 47; } bool MidiMessage::isTextMetaEvent() const noexcept { const int t = getMetaEventType(); return t > 0 && t < 16; } String MidiMessage::getTextFromTextMetaEvent() const { return String (reinterpret_cast (getMetaEventData()), getMetaEventLength()); } bool MidiMessage::isTrackNameEvent() const noexcept { return (data[1] == 3) && (*data == 0xff); } bool MidiMessage::isTempoMetaEvent() const noexcept { return (data[1] == 81) && (*data == 0xff); } bool MidiMessage::isMidiChannelMetaEvent() const noexcept { return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } int MidiMessage::getMidiChannelMetaEventChannel() const noexcept { jassert (isMidiChannelMetaEvent()); return data[3] + 1; } double MidiMessage::getTempoSecondsPerQuarterNote() const noexcept { if (! isTempoMetaEvent()) return 0.0; const uint8* const d = getMetaEventData(); return (((unsigned int) d[0] << 16) | ((unsigned int) d[1] << 8) | d[2]) / 1000000.0; } double MidiMessage::getTempoMetaEventTickLength (const short timeFormat) const noexcept { if (timeFormat > 0) { if (! isTempoMetaEvent()) return 0.5 / timeFormat; return getTempoSecondsPerQuarterNote() / timeFormat; } else { const int frameCode = (-timeFormat) >> 8; double framesPerSecond; switch (frameCode) { case 24: framesPerSecond = 24.0; break; case 25: framesPerSecond = 25.0; break; case 29: framesPerSecond = 29.97; break; case 30: framesPerSecond = 30.0; break; default: framesPerSecond = 30.0; break; } return (1.0 / framesPerSecond) / (timeFormat & 0xff); } } MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) noexcept { const uint8 d[] = { 0xff, 81, 3, (uint8) (microsecondsPerQuarterNote >> 16), (uint8) (microsecondsPerQuarterNote >> 8), (uint8) microsecondsPerQuarterNote }; return MidiMessage (d, 6, 0.0); } bool MidiMessage::isTimeSignatureMetaEvent() const noexcept { return (data[1] == 0x58) && (*data == (uint8) 0xff); } void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const noexcept { if (isTimeSignatureMetaEvent()) { const uint8* const d = getMetaEventData(); numerator = d[0]; denominator = 1 << d[1]; } else { numerator = 4; denominator = 4; } } MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) { int n = 1; int powerOfTwo = 0; while (n < denominator) { n <<= 1; ++powerOfTwo; } const uint8 d[] = { 0xff, 0x58, 0x04, (uint8) numerator, (uint8) powerOfTwo, 1, 96 }; return MidiMessage (d, 7, 0.0); } MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) noexcept { const uint8 d[] = { 0xff, 0x20, 0x01, (uint8) jlimit (0, 0xff, channel - 1) }; return MidiMessage (d, 4, 0.0); } bool MidiMessage::isKeySignatureMetaEvent() const noexcept { return getMetaEventType() == 89; } int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept { return (int) *getMetaEventData(); } MidiMessage MidiMessage::endOfTrack() noexcept { return MidiMessage (0xff, 0x2f, 0, 0.0); } bool MidiMessage::isSongPositionPointer() const noexcept { return *data == 0xf2; } int MidiMessage::getSongPositionPointerMidiBeat() const noexcept { return data[1] | (data[2] << 7); } MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) noexcept { return MidiMessage (0xf2, positionInMidiBeats & 127, (positionInMidiBeats >> 7) & 127); } bool MidiMessage::isMidiStart() const noexcept { return *data == 0xfa; } MidiMessage MidiMessage::midiStart() noexcept { return MidiMessage (0xfa); } bool MidiMessage::isMidiContinue() const noexcept { return *data == 0xfb; } MidiMessage MidiMessage::midiContinue() noexcept { return MidiMessage (0xfb); } bool MidiMessage::isMidiStop() const noexcept { return *data == 0xfc; } MidiMessage MidiMessage::midiStop() noexcept { return MidiMessage (0xfc); } bool MidiMessage::isMidiClock() const noexcept { return *data == 0xf8; } MidiMessage MidiMessage::midiClock() noexcept { return MidiMessage (0xf8); } bool MidiMessage::isQuarterFrame() const noexcept { return *data == 0xf1; } int MidiMessage::getQuarterFrameSequenceNumber() const noexcept { return ((int) data[1]) >> 4; } int MidiMessage::getQuarterFrameValue() const noexcept { return ((int) data[1]) & 0x0f; } MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) noexcept { return MidiMessage (0xf1, (sequenceNumber << 4) | value); } bool MidiMessage::isFullFrame() const noexcept { return data[0] == 0xf0 && data[1] == 0x7f && size >= 10 && data[3] == 0x01 && data[4] == 0x01; } void MidiMessage::getFullFrameParameters (int& hours, int& minutes, int& seconds, int& frames, MidiMessage::SmpteTimecodeType& timecodeType) const noexcept { jassert (isFullFrame()); timecodeType = (SmpteTimecodeType) (data[5] >> 5); hours = data[5] & 0x1f; minutes = data[6]; seconds = data[7]; frames = data[8]; } MidiMessage MidiMessage::fullFrame (const int hours, const int minutes, const int seconds, const int frames, MidiMessage::SmpteTimecodeType timecodeType) { const uint8 d[] = { 0xf0, 0x7f, 0x7f, 0x01, 0x01, (uint8) ((hours & 0x01f) | (timecodeType << 5)), (uint8) minutes, (uint8) seconds, (uint8) frames, 0xf7 }; return MidiMessage (d, 10, 0.0); } bool MidiMessage::isMidiMachineControlMessage() const noexcept { return data[0] == 0xf0 && data[1] == 0x7f && data[3] == 0x06 && size > 5; } MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const noexcept { jassert (isMidiMachineControlMessage()); return (MidiMachineControlCommand) data[4]; } MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) { const uint8 d[] = { 0xf0, 0x7f, 0, 6, (uint8) command, 0xf7 }; return MidiMessage (d, 6, 0.0); } bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const noexcept { if (size >= 12 && data[0] == 0xf0 && data[1] == 0x7f && data[3] == 0x06 && data[4] == 0x44 && data[5] == 0x06 && data[6] == 0x01) { hours = data[7] % 24; // (that some machines send out hours > 24) minutes = data[8]; seconds = data[9]; frames = data[10]; return true; } return false; } MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) { const uint8 d[] = { 0xf0, 0x7f, 0, 6, 0x44, 6, 1, (uint8) hours, (uint8) minutes, (uint8) seconds, (uint8) frames, 0xf7 }; return MidiMessage (d, 12, 0.0); } String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) { static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; if (isPositiveAndBelow (note, (int) 128)) { String s (useSharps ? sharpNoteNames [note % 12] : flatNoteNames [note % 12]); if (includeOctaveNumber) s << (note / 12 + (octaveNumForMiddleC - 5)); return s; } return String::empty; } const double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept { noteNumber -= 12 * 6 + 9; // now 0 = A return frequencyOfA * pow (2.0, noteNumber / 12.0); } String MidiMessage::getGMInstrumentName (const int n) { const char* names[] = { "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (mute)", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot" }; return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) 0; } String MidiMessage::getGMInstrumentBankName (const int n) { const char* names[] = { "Piano", "Chromatic Percussion", "Organ", "Guitar", "Bass", "Strings", "Ensemble", "Brass", "Reed", "Pipe", "Synth Lead", "Synth Pad", "Synth Effects", "Ethnic", "Percussive", "Sound Effects" }; return isPositiveAndBelow (n, (int) 16) ? names[n] : (const char*) 0; } String MidiMessage::getRhythmInstrumentName (const int n) { const char* names[] = { "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi-Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi-Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle" }; return (n >= 35 && n <= 81) ? names [n - 35] : (const char*) nullptr; } String MidiMessage::getControllerName (const int n) { const char* names[] = { "Bank Select", "Modulation Wheel (coarse)", "Breath controller (coarse)", 0, "Foot Pedal (coarse)", "Portamento Time (coarse)", "Data Entry (coarse)", "Volume (coarse)", "Balance (coarse)", 0, "Pan position (coarse)", "Expression (coarse)", "Effect Control 1 (coarse)", "Effect Control 2 (coarse)", 0, 0, "General Purpose Slider 1", "General Purpose Slider 2", "General Purpose Slider 3", "General Purpose Slider 4", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Bank Select (fine)", "Modulation Wheel (fine)", "Breath controller (fine)", 0, "Foot Pedal (fine)", "Portamento Time (fine)", "Data Entry (fine)", "Volume (fine)", "Balance (fine)", 0, "Pan position (fine)", "Expression (fine)", "Effect Control 1 (fine)", "Effect Control 2 (fine)", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Hold Pedal (on/off)", "Portamento (on/off)", "Sustenuto Pedal (on/off)", "Soft Pedal (on/off)", "Legato Pedal (on/off)", "Hold 2 Pedal (on/off)", "Sound Variation", "Sound Timbre", "Sound Release Time", "Sound Attack Time", "Sound Brightness", "Sound Control 6", "Sound Control 7", "Sound Control 8", "Sound Control 9", "Sound Control 10", "General Purpose Button 1 (on/off)", "General Purpose Button 2 (on/off)", "General Purpose Button 3 (on/off)", "General Purpose Button 4 (on/off)", 0, 0, 0, 0, 0, 0, 0, "Reverb Level", "Tremolo Level", "Chorus Level", "Celeste Level", "Phaser Level", "Data Button increment", "Data Button decrement", "Non-registered Parameter (fine)", "Non-registered Parameter (coarse)", "Registered Parameter (fine)", "Registered Parameter (coarse)", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "All Sound Off", "All Controllers Off", "Local Keyboard (on/off)", "All Notes Off", "Omni Mode Off", "Omni Mode On", "Mono Operation", "Poly Operation" }; return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessage.cpp ***/ /*** Start of inlined file: juce_MidiMessageCollector.cpp ***/ BEGIN_JUCE_NAMESPACE MidiMessageCollector::MidiMessageCollector() : lastCallbackTime (0), sampleRate (44100.0001) { } MidiMessageCollector::~MidiMessageCollector() { } void MidiMessageCollector::reset (const double sampleRate_) { jassert (sampleRate_ > 0); const ScopedLock sl (midiCallbackLock); sampleRate = sampleRate_; incomingMessages.clear(); lastCallbackTime = Time::getMillisecondCounterHiRes(); } void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) { // you need to call reset() to set the correct sample rate before using this object jassert (sampleRate != 44100.0001); // the messages that come in here need to be time-stamped correctly - see MidiInput // for details of what the number should be. jassert (message.getTimeStamp() != 0); const ScopedLock sl (midiCallbackLock); const int sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); incomingMessages.addEvent (message, sampleNumber); // if the messages don't get used for over a second, we'd better // get rid of any old ones to avoid the queue getting too big if (sampleNumber > sampleRate) incomingMessages.clear (0, sampleNumber - (int) sampleRate); } void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, const int numSamples) { // you need to call reset() to set the correct sample rate before using this object jassert (sampleRate != 44100.0001); const double timeNow = Time::getMillisecondCounterHiRes(); const double msElapsed = timeNow - lastCallbackTime; const ScopedLock sl (midiCallbackLock); lastCallbackTime = timeNow; if (! incomingMessages.isEmpty()) { int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); int startSample = 0; int scale = 1 << 16; const uint8* midiData; int numBytes, samplePosition; MidiBuffer::Iterator iter (incomingMessages); if (numSourceSamples > numSamples) { // if our list of events is longer than the buffer we're being // asked for, scale them down to squeeze them all in.. const int maxBlockLengthToUse = numSamples << 5; if (numSourceSamples > maxBlockLengthToUse) { startSample = numSourceSamples - maxBlockLengthToUse; numSourceSamples = maxBlockLengthToUse; iter.setNextSamplePosition (startSample); } scale = (numSamples << 10) / numSourceSamples; while (iter.getNextEvent (midiData, numBytes, samplePosition)) { samplePosition = ((samplePosition - startSample) * scale) >> 10; destBuffer.addEvent (midiData, numBytes, jlimit (0, numSamples - 1, samplePosition)); } } else { // if our event list is shorter than the number we need, put them // towards the end of the buffer startSample = numSamples - numSourceSamples; while (iter.getNextEvent (midiData, numBytes, samplePosition)) { destBuffer.addEvent (midiData, numBytes, jlimit (0, numSamples - 1, samplePosition + startSample)); } } incomingMessages.clear(); } } void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) { MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); addMessageToQueue (m); } void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) { MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); addMessageToQueue (m); } void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) { addMessageToQueue (message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessageCollector.cpp ***/ /*** Start of inlined file: juce_MidiMessageSequence.cpp ***/ BEGIN_JUCE_NAMESPACE MidiMessageSequence::MidiMessageSequence() { } MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) { list.ensureStorageAllocated (other.list.size()); for (int i = 0; i < other.list.size(); ++i) list.add (new MidiEventHolder (other.list.getUnchecked(i)->message)); } MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) { MidiMessageSequence otherCopy (other); swapWith (otherCopy); return *this; } void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept { list.swapWithArray (other.list); } MidiMessageSequence::~MidiMessageSequence() { } void MidiMessageSequence::clear() { list.clear(); } int MidiMessageSequence::getNumEvents() const { return list.size(); } MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const { return list [index]; } double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const { const MidiEventHolder* const meh = list [index]; if (meh != nullptr && meh->noteOffObject != nullptr) return meh->noteOffObject->message.getTimeStamp(); else return 0.0; } int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const { const MidiEventHolder* const meh = list [index]; return meh != nullptr ? list.indexOf (meh->noteOffObject) : -1; } int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const { return list.indexOf (event); } int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const { const int numEvents = list.size(); int i; for (i = 0; i < numEvents; ++i) if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) break; return i; } double MidiMessageSequence::getStartTime() const { if (list.size() > 0) return list.getUnchecked(0)->message.getTimeStamp(); else return 0; } double MidiMessageSequence::getEndTime() const { if (list.size() > 0) return list.getLast()->message.getTimeStamp(); else return 0; } double MidiMessageSequence::getEventTime (const int index) const { if (isPositiveAndBelow (index, list.size())) return list.getUnchecked (index)->message.getTimeStamp(); return 0.0; } void MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) { MidiEventHolder* const newOne = new MidiEventHolder (newMessage); timeAdjustment += newMessage.getTimeStamp(); newOne->message.setTimeStamp (timeAdjustment); int i; for (i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment) break; list.insert (i + 1, newOne); } void MidiMessageSequence::deleteEvent (const int index, const bool deleteMatchingNoteUp) { if (isPositiveAndBelow (index, list.size())) { if (deleteMatchingNoteUp) deleteEvent (getIndexOfMatchingKeyUp (index), false); list.remove (index); } } void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment, double firstAllowableTime, double endOfAllowableDestTimes) { firstAllowableTime -= timeAdjustment; endOfAllowableDestTimes -= timeAdjustment; for (int i = 0; i < other.list.size(); ++i) { const MidiMessage& m = other.list.getUnchecked(i)->message; const double t = m.getTimeStamp(); if (t >= firstAllowableTime && t < endOfAllowableDestTimes) { MidiEventHolder* const newOne = new MidiEventHolder (m); newOne->message.setTimeStamp (timeAdjustment + t); list.add (newOne); } } sort(); } int MidiMessageSequence::compareElements (const MidiMessageSequence::MidiEventHolder* const first, const MidiMessageSequence::MidiEventHolder* const second) noexcept { const double diff = first->message.getTimeStamp() - second->message.getTimeStamp(); return (diff > 0) - (diff < 0); } void MidiMessageSequence::sort() { list.sort (*this, true); } void MidiMessageSequence::updateMatchedPairs() { for (int i = 0; i < list.size(); ++i) { const MidiMessage& m1 = list.getUnchecked(i)->message; if (m1.isNoteOn()) { list.getUnchecked(i)->noteOffObject = nullptr; const int note = m1.getNoteNumber(); const int chan = m1.getChannel(); const int len = list.size(); for (int j = i + 1; j < len; ++j) { const MidiMessage& m = list.getUnchecked(j)->message; if (m.getNoteNumber() == note && m.getChannel() == chan) { if (m.isNoteOff()) { list.getUnchecked(i)->noteOffObject = list[j]; break; } else if (m.isNoteOn()) { list.insert (j, new MidiEventHolder (MidiMessage::noteOff (chan, note))); list.getUnchecked(j)->message.setTimeStamp (m.getTimeStamp()); list.getUnchecked(i)->noteOffObject = list[j]; break; } } } } } } void MidiMessageSequence::addTimeToMessages (const double delta) { for (int i = list.size(); --i >= 0;) list.getUnchecked (i)->message.setTimeStamp (list.getUnchecked (i)->message.getTimeStamp() + delta); } void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, MidiMessageSequence& destSequence, const bool alsoIncludeMetaEvents) const { for (int i = 0; i < list.size(); ++i) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent())) { destSequence.addEvent (mm); } } } void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const { for (int i = 0; i < list.size(); ++i) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isSysEx()) destSequence.addEvent (mm); } } void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) { for (int i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) list.remove(i); } void MidiMessageSequence::deleteSysExMessages() { for (int i = list.size(); --i >= 0;) if (list.getUnchecked(i)->message.isSysEx()) list.remove(i); } void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber, const double time, OwnedArray& dest) { bool doneProg = false; bool donePitchWheel = false; Array doneControllers; doneControllers.ensureStorageAllocated (32); for (int i = list.size(); --i >= 0;) { const MidiMessage& mm = list.getUnchecked(i)->message; if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) { if (mm.isProgramChange()) { if (! doneProg) { dest.add (new MidiMessage (mm, 0.0)); doneProg = true; } } else if (mm.isController()) { if (! doneControllers.contains (mm.getControllerNumber())) { dest.add (new MidiMessage (mm, 0.0)); doneControllers.add (mm.getControllerNumber()); } } else if (mm.isPitchWheel()) { if (! donePitchWheel) { dest.add (new MidiMessage (mm, 0.0)); donePitchWheel = true; } } } } } MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& message_) : message (message_), noteOffObject (nullptr) { } MidiMessageSequence::MidiEventHolder::~MidiEventHolder() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_MidiMessageSequence.cpp ***/ /*** Start of inlined file: juce_AudioPluginFormat.cpp ***/ BEGIN_JUCE_NAMESPACE AudioPluginFormat::AudioPluginFormat() noexcept { } AudioPluginFormat::~AudioPluginFormat() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginFormat.cpp ***/ /*** Start of inlined file: juce_AudioPluginFormatManager.cpp ***/ BEGIN_JUCE_NAMESPACE AudioPluginFormatManager::AudioPluginFormatManager() { } AudioPluginFormatManager::~AudioPluginFormatManager() { clearSingletonInstance(); } juce_ImplementSingleton_SingleThreaded (AudioPluginFormatManager); void AudioPluginFormatManager::addDefaultFormats() { #if JUCE_DEBUG // you should only call this method once! for (int i = formats.size(); --i >= 0;) { #if JUCE_PLUGINHOST_VST && ! (JUCE_MAC && JUCE_64BIT) jassert (dynamic_cast (formats[i]) == nullptr); #endif #if JUCE_PLUGINHOST_AU && JUCE_MAC jassert (dynamic_cast (formats[i]) == nullptr); #endif #if JUCE_PLUGINHOST_DX && JUCE_WINDOWS jassert (dynamic_cast (formats[i]) == nullptr); #endif #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX jassert (dynamic_cast (formats[i]) == nullptr); #endif } #endif #if JUCE_PLUGINHOST_AU && JUCE_MAC formats.add (new AudioUnitPluginFormat()); #endif #if JUCE_PLUGINHOST_VST && ! (JUCE_MAC && JUCE_64BIT) formats.add (new VSTPluginFormat()); #endif #if JUCE_PLUGINHOST_DX && JUCE_WINDOWS formats.add (new DirectXPluginFormat()); #endif #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX formats.add (new LADSPAPluginFormat()); #endif } int AudioPluginFormatManager::getNumFormats() { return formats.size(); } AudioPluginFormat* AudioPluginFormatManager::getFormat (const int index) { return formats [index]; } void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) { formats.add (format); } AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, String& errorMessage) const { AudioPluginInstance* result = nullptr; for (int i = 0; i < formats.size(); ++i) { result = formats.getUnchecked(i)->createInstanceFromDescription (description); if (result != nullptr) break; } if (result == nullptr) { if (! doesPluginStillExist (description)) errorMessage = TRANS ("This plug-in file no longer exists"); else errorMessage = TRANS ("This plug-in failed to load correctly"); } return result; } bool AudioPluginFormatManager::doesPluginStillExist (const PluginDescription& description) const { for (int i = 0; i < formats.size(); ++i) if (formats.getUnchecked(i)->getName() == description.pluginFormatName) return formats.getUnchecked(i)->doesPluginStillExist (description); return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginFormatManager.cpp ***/ /*** Start of inlined file: juce_AudioPluginInstance.cpp ***/ #define JUCE_PLUGIN_HOST 1 BEGIN_JUCE_NAMESPACE AudioPluginInstance::AudioPluginInstance() { } AudioPluginInstance::~AudioPluginInstance() { } void* AudioPluginInstance::getPlatformSpecificData() { return nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioPluginInstance.cpp ***/ /*** Start of inlined file: juce_KnownPluginList.cpp ***/ BEGIN_JUCE_NAMESPACE KnownPluginList::KnownPluginList() { } KnownPluginList::~KnownPluginList() { } void KnownPluginList::clear() { if (types.size() > 0) { types.clear(); sendChangeMessage(); } } PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const { for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) return types.getUnchecked(i); return nullptr; } PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const { for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->createIdentifierString() == identifierString) return types.getUnchecked(i); return nullptr; } bool KnownPluginList::addType (const PluginDescription& type) { for (int i = types.size(); --i >= 0;) { if (types.getUnchecked(i)->isDuplicateOf (type)) { // strange - found a duplicate plugin with different info.. jassert (types.getUnchecked(i)->name == type.name); jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); *types.getUnchecked(i) = type; return false; } } types.add (new PluginDescription (type)); sendChangeMessage(); return true; } void KnownPluginList::removeType (const int index) { types.remove (index); sendChangeMessage(); } namespace { const Time getPluginFileModTime (const String& fileOrIdentifier) { if (fileOrIdentifier.startsWithChar ('/') || fileOrIdentifier[1] == ':') return File (fileOrIdentifier).getLastModificationTime(); return Time(); } bool timesAreDifferent (const Time& t1, const Time& t2) noexcept { return t1 != t2 || t1 == Time(); } } bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier) const { if (getTypeForFile (fileOrIdentifier) == 0) return false; for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); if (d->fileOrIdentifier == fileOrIdentifier && timesAreDifferent (d->lastFileModTime, getPluginFileModTime (fileOrIdentifier))) { return false; } } return true; } bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, const bool dontRescanIfAlreadyInList, OwnedArray & typesFound, AudioPluginFormat& format) { bool addedOne = false; if (dontRescanIfAlreadyInList && getTypeForFile (fileOrIdentifier) != nullptr) { bool needsRescanning = false; for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); if (d->fileOrIdentifier == fileOrIdentifier) { if (timesAreDifferent (d->lastFileModTime, getPluginFileModTime (fileOrIdentifier))) needsRescanning = true; else typesFound.add (new PluginDescription (*d)); } } if (! needsRescanning) return false; } OwnedArray found; format.findAllTypesForFile (found, fileOrIdentifier); for (int i = 0; i < found.size(); ++i) { PluginDescription* const desc = found.getUnchecked(i); jassert (desc != nullptr); if (addType (*desc)) addedOne = true; typesFound.add (new PluginDescription (*desc)); } return addedOne; } void KnownPluginList::scanAndAddDragAndDroppedFiles (const StringArray& files, OwnedArray & typesFound) { for (int i = 0; i < files.size(); ++i) { for (int j = 0; j < AudioPluginFormatManager::getInstance()->getNumFormats(); ++j) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (j); if (scanAndAddFile (files[i], true, typesFound, *format)) return; } const File f (files[i]); if (f.isDirectory()) { StringArray s; { Array subFiles; f.findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int j = 0; j < subFiles.size(); ++j) s.add (subFiles.getReference(j).getFullPathName()); } scanAndAddDragAndDroppedFiles (s, typesFound); } } } class PluginSorter { public: KnownPluginList::SortMethod method; PluginSorter() noexcept {} int compareElements (const PluginDescription* const first, const PluginDescription* const second) const { int diff = 0; if (method == KnownPluginList::sortByCategory) diff = first->category.compareLexicographically (second->category); else if (method == KnownPluginList::sortByManufacturer) diff = first->manufacturerName.compareLexicographically (second->manufacturerName); else if (method == KnownPluginList::sortByFileSystemLocation) diff = first->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false) .compare (second->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false)); if (diff == 0) diff = first->name.compareLexicographically (second->name); return diff; } }; void KnownPluginList::sort (const SortMethod method) { if (method != defaultOrder) { PluginSorter sorter; sorter.method = method; types.sort (sorter, true); sendChangeMessage(); } } XmlElement* KnownPluginList::createXml() const { XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); for (int i = 0; i < types.size(); ++i) e->addChildElement (types.getUnchecked(i)->createXml()); return e; } void KnownPluginList::recreateFromXml (const XmlElement& xml) { clear(); if (xml.hasTagName ("KNOWNPLUGINS")) { forEachXmlChildElement (xml, e) { PluginDescription info; if (info.loadFromXml (*e)) addType (info); } } } const int menuIdBase = 0x324503f4; // This is used to turn a bunch of paths into a nested menu structure. struct PluginFilesystemTree { private: String folder; OwnedArray subFolders; Array plugins; void addPlugin (PluginDescription* const pd, const String& path) { if (path.isEmpty()) { plugins.add (pd); } else { const String firstSubFolder (path.upToFirstOccurrenceOf ("/", false, false)); const String remainingPath (path.fromFirstOccurrenceOf ("/", false, false)); for (int i = subFolders.size(); --i >= 0;) { if (subFolders.getUnchecked(i)->folder.equalsIgnoreCase (firstSubFolder)) { subFolders.getUnchecked(i)->addPlugin (pd, remainingPath); return; } } PluginFilesystemTree* const newFolder = new PluginFilesystemTree(); newFolder->folder = firstSubFolder; subFolders.add (newFolder); newFolder->addPlugin (pd, remainingPath); } } // removes any deeply nested folders that don't contain any actual plugins void optimise() { for (int i = subFolders.size(); --i >= 0;) { PluginFilesystemTree* const sub = subFolders.getUnchecked(i); sub->optimise(); if (sub->plugins.size() == 0) { for (int j = 0; j < sub->subFolders.size(); ++j) subFolders.add (sub->subFolders.getUnchecked(j)); sub->subFolders.clear (false); subFolders.remove (i); } } } public: void buildTree (const Array & allPlugins) { for (int i = 0; i < allPlugins.size(); ++i) { String path (allPlugins.getUnchecked(i) ->fileOrIdentifier.replaceCharacter ('\\', '/') .upToLastOccurrenceOf ("/", false, false)); if (path.substring (1, 2) == ":") path = path.substring (2); addPlugin (allPlugins.getUnchecked(i), path); } optimise(); } void addToMenu (PopupMenu& m, const OwnedArray & allPlugins) const { int i; for (i = 0; i < subFolders.size(); ++i) { const PluginFilesystemTree* const sub = subFolders.getUnchecked(i); PopupMenu subMenu; sub->addToMenu (subMenu, allPlugins); #if JUCE_MAC // avoid the special AU formatting nonsense on Mac.. m.addSubMenu (sub->folder.fromFirstOccurrenceOf (":", false, false), subMenu); #else m.addSubMenu (sub->folder, subMenu); #endif } for (i = 0; i < plugins.size(); ++i) { PluginDescription* const plugin = plugins.getUnchecked(i); m.addItem (allPlugins.indexOf (plugin) + menuIdBase, plugin->name, true, false); } } }; void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const { Array sorted; { PluginSorter sorter; sorter.method = sortMethod; for (int i = 0; i < types.size(); ++i) sorted.addSorted (sorter, types.getUnchecked(i)); } if (sortMethod == sortByCategory || sortMethod == sortByManufacturer) { String lastSubMenuName; PopupMenu sub; for (int i = 0; i < sorted.size(); ++i) { const PluginDescription* const pd = sorted.getUnchecked(i); String thisSubMenuName (sortMethod == sortByCategory ? pd->category : pd->manufacturerName); if (! thisSubMenuName.containsNonWhitespaceChars()) thisSubMenuName = "Other"; if (thisSubMenuName != lastSubMenuName) { if (sub.getNumItems() > 0) { menu.addSubMenu (lastSubMenuName, sub); sub.clear(); } lastSubMenuName = thisSubMenuName; } sub.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); } if (sub.getNumItems() > 0) menu.addSubMenu (lastSubMenuName, sub); } else if (sortMethod == sortByFileSystemLocation) { PluginFilesystemTree root; root.buildTree (sorted); root.addToMenu (menu, types); } else { for (int i = 0; i < sorted.size(); ++i) { const PluginDescription* const pd = sorted.getUnchecked(i); menu.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); } } } int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const { const int i = menuResultCode - menuIdBase; return isPositiveAndBelow (i, types.size()) ? i : -1; } END_JUCE_NAMESPACE /*** End of inlined file: juce_KnownPluginList.cpp ***/ /*** Start of inlined file: juce_PluginDescription.cpp ***/ BEGIN_JUCE_NAMESPACE PluginDescription::PluginDescription() : uid (0), isInstrument (false), numInputChannels (0), numOutputChannels (0) { } PluginDescription::~PluginDescription() { } PluginDescription::PluginDescription (const PluginDescription& other) : name (other.name), descriptiveName (other.descriptiveName), pluginFormatName (other.pluginFormatName), category (other.category), manufacturerName (other.manufacturerName), version (other.version), fileOrIdentifier (other.fileOrIdentifier), lastFileModTime (other.lastFileModTime), uid (other.uid), isInstrument (other.isInstrument), numInputChannels (other.numInputChannels), numOutputChannels (other.numOutputChannels) { } PluginDescription& PluginDescription::operator= (const PluginDescription& other) { name = other.name; descriptiveName = other.descriptiveName; pluginFormatName = other.pluginFormatName; category = other.category; manufacturerName = other.manufacturerName; version = other.version; fileOrIdentifier = other.fileOrIdentifier; uid = other.uid; isInstrument = other.isInstrument; lastFileModTime = other.lastFileModTime; numInputChannels = other.numInputChannels; numOutputChannels = other.numOutputChannels; return *this; } bool PluginDescription::isDuplicateOf (const PluginDescription& other) const { return fileOrIdentifier == other.fileOrIdentifier && uid == other.uid; } const String PluginDescription::createIdentifierString() const { return pluginFormatName + "-" + name + "-" + String::toHexString (fileOrIdentifier.hashCode()) + "-" + String::toHexString (uid); } XmlElement* PluginDescription::createXml() const { XmlElement* const e = new XmlElement ("PLUGIN"); e->setAttribute ("name", name); if (descriptiveName != name) e->setAttribute ("descriptiveName", descriptiveName); e->setAttribute ("format", pluginFormatName); e->setAttribute ("category", category); e->setAttribute ("manufacturer", manufacturerName); e->setAttribute ("version", version); e->setAttribute ("file", fileOrIdentifier); e->setAttribute ("uid", String::toHexString (uid)); e->setAttribute ("isInstrument", isInstrument); e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); e->setAttribute ("numInputs", numInputChannels); e->setAttribute ("numOutputs", numOutputChannels); return e; } bool PluginDescription::loadFromXml (const XmlElement& xml) { if (xml.hasTagName ("PLUGIN")) { name = xml.getStringAttribute ("name"); descriptiveName = xml.getStringAttribute ("name", name); pluginFormatName = xml.getStringAttribute ("format"); category = xml.getStringAttribute ("category"); manufacturerName = xml.getStringAttribute ("manufacturer"); version = xml.getStringAttribute ("version"); fileOrIdentifier = xml.getStringAttribute ("file"); uid = xml.getStringAttribute ("uid").getHexValue32(); isInstrument = xml.getBoolAttribute ("isInstrument", false); lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); numInputChannels = xml.getIntAttribute ("numInputs"); numOutputChannels = xml.getIntAttribute ("numOutputs"); return true; } return false; } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginDescription.cpp ***/ /*** Start of inlined file: juce_PluginDirectoryScanner.cpp ***/ BEGIN_JUCE_NAMESPACE PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, AudioPluginFormat& formatToLookFor, FileSearchPath directoriesToSearch, const bool recursive, const File& deadMansPedalFile_) : list (listToAddTo), format (formatToLookFor), deadMansPedalFile (deadMansPedalFile_), nextIndex (0), progress (0) { directoriesToSearch.removeRedundantPaths(); filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); // If any plugins have crashed recently when being loaded, move them to the // end of the list to give the others a chance to load correctly.. const StringArray crashedPlugins (getDeadMansPedalFile()); for (int i = 0; i < crashedPlugins.size(); ++i) { const String f = crashedPlugins[i]; for (int j = filesOrIdentifiersToScan.size(); --j >= 0;) if (f == filesOrIdentifiersToScan[j]) filesOrIdentifiersToScan.move (j, -1); } } PluginDirectoryScanner::~PluginDirectoryScanner() { } const String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const { return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex]); } bool PluginDirectoryScanner::scanNextFile (const bool dontRescanIfAlreadyInList) { String file (filesOrIdentifiersToScan [nextIndex]); if (file.isNotEmpty() && ! list.isListingUpToDate (file)) { OwnedArray typesFound; // Add this plugin to the end of the dead-man's pedal list in case it crashes... StringArray crashedPlugins (getDeadMansPedalFile()); crashedPlugins.removeString (file); crashedPlugins.add (file); setDeadMansPedalFile (crashedPlugins); list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format); // Managed to load without crashing, so remove it from the dead-man's-pedal.. crashedPlugins.removeString (file); setDeadMansPedalFile (crashedPlugins); if (typesFound.size() == 0) failedFiles.add (file); } return skipNextFile(); } bool PluginDirectoryScanner::skipNextFile() { if (nextIndex >= filesOrIdentifiersToScan.size()) return false; progress = ++nextIndex / (float) filesOrIdentifiersToScan.size(); return nextIndex < filesOrIdentifiersToScan.size(); } StringArray PluginDirectoryScanner::getDeadMansPedalFile() { StringArray lines; if (deadMansPedalFile != File::nonexistent) { lines.addLines (deadMansPedalFile.loadFileAsString()); lines.removeEmptyStrings(); } return lines; } void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) { if (deadMansPedalFile != File::nonexistent) deadMansPedalFile.replaceWithText (newContents.joinIntoString ("\n"), true, true); } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginDirectoryScanner.cpp ***/ /*** Start of inlined file: juce_PluginListComponent.cpp ***/ BEGIN_JUCE_NAMESPACE PluginListComponent::PluginListComponent (KnownPluginList& listToEdit, const File& deadMansPedalFile_, PropertiesFile* const propertiesToUse_) : list (listToEdit), deadMansPedalFile (deadMansPedalFile_), optionsButton ("Options..."), propertiesToUse (propertiesToUse_) { listBox.setModel (this); addAndMakeVisible (&listBox); addAndMakeVisible (&optionsButton); optionsButton.addListener (this); optionsButton.setTriggeredOnMouseDown (true); setSize (400, 600); list.addChangeListener (this); updateList(); } PluginListComponent::~PluginListComponent() { list.removeChangeListener (this); } void PluginListComponent::resized() { listBox.setBounds (0, 0, getWidth(), getHeight() - 30); optionsButton.changeWidthToFitText (24); optionsButton.setTopLeftPosition (8, getHeight() - 28); } void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) { updateList(); } void PluginListComponent::updateList() { listBox.updateContent(); listBox.repaint(); } int PluginListComponent::getNumRows() { return list.getNumTypes(); } void PluginListComponent::paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId)); const PluginDescription* const pd = list.getType (row); if (pd != nullptr) { GlyphArrangement ga; ga.addCurtailedLineOfText (Font (height * 0.7f, Font::bold), pd->name, 8.0f, height * 0.8f, width - 10.0f, true); g.setColour (Colours::black); ga.draw (g); const Rectangle bb (ga.getBoundingBox (0, -1, false)); String desc; desc << pd->pluginFormatName << (pd->isInstrument ? " instrument" : " effect") << " - " << pd->numInputChannels << (pd->numInputChannels == 1 ? " in" : " ins") << " / " << pd->numOutputChannels << (pd->numOutputChannels == 1 ? " out" : " outs"); if (pd->manufacturerName.isNotEmpty()) desc << " - " << pd->manufacturerName; if (pd->version.isNotEmpty()) desc << " - " << pd->version; if (pd->category.isNotEmpty()) desc << " - category: '" << pd->category << '\''; g.setColour (Colours::grey); ga.clear(); ga.addCurtailedLineOfText (Font (height * 0.6f), desc, bb.getRight() + 10.0f, height * 0.8f, width - bb.getRight() - 12.0f, true); ga.draw (g); } } void PluginListComponent::deleteKeyPressed (int lastRowSelected) { list.removeType (lastRowSelected); } void PluginListComponent::optionsMenuCallback (int result) { switch (result) { case 1: list.clear(); break; case 2: list.sort (KnownPluginList::sortAlphabetically); break; case 3: list.sort (KnownPluginList::sortByCategory); break; case 4: list.sort (KnownPluginList::sortByManufacturer); break; case 5: { const SparseSet selected (listBox.getSelectedRows()); for (int i = list.getNumTypes(); --i >= 0;) if (selected.contains (i)) list.removeType (i); break; } case 6: { const PluginDescription* const desc = list.getType (listBox.getSelectedRow()); if (desc != nullptr) { if (File (desc->fileOrIdentifier).existsAsFile()) File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); } break; } case 7: for (int i = list.getNumTypes(); --i >= 0;) if (! AudioPluginFormatManager::getInstance()->doesPluginStillExist (*list.getType (i))) list.removeType (i); break; default: if (result != 0) { typeToScan = result - 10; startTimer (1); } break; } } void PluginListComponent::optionsMenuStaticCallback (int result, PluginListComponent* pluginList) { if (pluginList != nullptr) pluginList->optionsMenuCallback (result); } void PluginListComponent::buttonClicked (Button* button) { if (button == &optionsButton) { PopupMenu menu; menu.addItem (1, TRANS("Clear list")); menu.addItem (5, TRANS("Remove selected plugin from list"), listBox.getNumSelectedRows() > 0); menu.addItem (6, TRANS("Show folder containing selected plugin"), listBox.getNumSelectedRows() > 0); menu.addItem (7, TRANS("Remove any plugins whose files no longer exist")); menu.addSeparator(); menu.addItem (2, TRANS("Sort alphabetically")); menu.addItem (3, TRANS("Sort by category")); menu.addItem (4, TRANS("Sort by manufacturer")); menu.addSeparator(); for (int i = 0; i < AudioPluginFormatManager::getInstance()->getNumFormats(); ++i) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (i); if (format->getDefaultLocationsToSearch().getNumPaths() > 0) menu.addItem (10 + i, "Scan for new or updated " + format->getName() + " plugins..."); } menu.showMenuAsync (PopupMenu::Options().withTargetComponent (&optionsButton), ModalCallbackFunction::forComponent (optionsMenuStaticCallback, this)); } } void PluginListComponent::timerCallback() { stopTimer(); scanFor (AudioPluginFormatManager::getInstance()->getFormat (typeToScan)); } bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/) { return true; } void PluginListComponent::filesDropped (const StringArray& files, int, int) { OwnedArray typesFound; list.scanAndAddDragAndDroppedFiles (files, typesFound); } void PluginListComponent::scanFor (AudioPluginFormat* format) { #if JUCE_MODAL_LOOPS_PERMITTED if (format == nullptr) return; FileSearchPath path (format->getDefaultLocationsToSearch()); if (propertiesToUse != nullptr) path = propertiesToUse->getValue ("lastPluginScanPath_" + format->getName(), path.toString()); { AlertWindow aw (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon); FileSearchPathListComponent pathList; pathList.setSize (500, 300); pathList.setPath (path); aw.addCustomComponent (&pathList); aw.addButton (TRANS("Scan"), 1, KeyPress::returnKey); aw.addButton (TRANS("Cancel"), 0, KeyPress::escapeKey); if (aw.runModalLoop() == 0) return; path = pathList.getPath(); } if (propertiesToUse != nullptr) { propertiesToUse->setValue ("lastPluginScanPath_" + format->getName(), path.toString()); propertiesToUse->saveIfNeeded(); } double progress = 0.0; AlertWindow aw (TRANS("Scanning for plugins..."), TRANS("Searching for all possible plugin files..."), AlertWindow::NoIcon); aw.addButton (TRANS("Cancel"), 0, KeyPress::escapeKey); aw.addProgressBarComponent (progress); aw.enterModalState(); MessageManager::getInstance()->runDispatchLoopUntil (300); PluginDirectoryScanner scanner (list, *format, path, true, deadMansPedalFile); for (;;) { aw.setMessage (TRANS("Testing:\n\n") + scanner.getNextPluginFileThatWillBeScanned()); MessageManager::getInstance()->runDispatchLoopUntil (20); if (! scanner.scanNextFile (true)) break; if (! aw.isCurrentlyModal()) break; progress = scanner.getProgress(); } if (scanner.getFailedFiles().size() > 0) { StringArray shortNames; for (int i = 0; i < scanner.getFailedFiles().size(); ++i) shortNames.add (File (scanner.getFailedFiles()[i]).getFileName()); AlertWindow::showMessageBox (AlertWindow::InfoIcon, TRANS("Scan complete"), TRANS("Note that the following files appeared to be plugin files, but failed to load correctly:\n\n") + shortNames.joinIntoString (", ")); } #else jassertfalse; // this method needs refactoring to work without modal loops.. #endif } END_JUCE_NAMESPACE /*** End of inlined file: juce_PluginListComponent.cpp ***/ /*** Start of inlined file: juce_AudioUnitPluginFormat.mm ***/ #if JUCE_PLUGINHOST_AU && ! (JUCE_LINUX || JUCE_WINDOWS) #include #include #include #if JUCE_SUPPORT_CARBON #include #include #endif BEGIN_JUCE_NAMESPACE #if JUCE_MAC && JUCE_SUPPORT_CARBON #endif #if JUCE_MAC // Change this to disable logging of various activities #ifndef AU_LOGGING #define AU_LOGGING 1 #endif #if AU_LOGGING #define log(a) Logger::writeToLog(a); #else #define log(a) #endif namespace AudioUnitFormatHelpers { static int insideCallback = 0; const String osTypeToString (OSType type) { const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), (juce_wchar) ((type >> 16) & 0xff), (juce_wchar) ((type >> 8) & 0xff), (juce_wchar) (type & 0xff) }; return String (s, 4); } OSType stringToOSType (const String& s1) { const String s (s1 + " "); return (((OSType) (unsigned char) s[0]) << 24) | (((OSType) (unsigned char) s[1]) << 16) | (((OSType) (unsigned char) s[2]) << 8) | ((OSType) (unsigned char) s[3]); } static const char* auIdentifierPrefix = "AudioUnit:"; const String createAUPluginIdentifier (const ComponentDescription& desc) { jassert (osTypeToString ('abcd') == "abcd"); // agh, must have got the endianness wrong.. jassert (stringToOSType ("abcd") == (OSType) 'abcd'); // ditto String s (auIdentifierPrefix); if (desc.componentType == kAudioUnitType_MusicDevice) s << "Synths/"; else if (desc.componentType == kAudioUnitType_MusicEffect || desc.componentType == kAudioUnitType_Effect) s << "Effects/"; else if (desc.componentType == kAudioUnitType_Generator) s << "Generators/"; else if (desc.componentType == kAudioUnitType_Panner) s << "Panners/"; s << osTypeToString (desc.componentType) << "," << osTypeToString (desc.componentSubType) << "," << osTypeToString (desc.componentManufacturer); return s; } void getAUDetails (ComponentRecord* comp, String& name, String& manufacturer) { Handle componentNameHandle = NewHandle (sizeof (void*)); Handle componentInfoHandle = NewHandle (sizeof (void*)); if (componentNameHandle != 0 && componentInfoHandle != 0) { ComponentDescription desc; if (GetComponentInfo (comp, &desc, componentNameHandle, componentInfoHandle, 0) == noErr) { ConstStr255Param nameString = (ConstStr255Param) (*componentNameHandle); ConstStr255Param infoString = (ConstStr255Param) (*componentInfoHandle); if (nameString != 0 && nameString[0] != 0) { const String all ((const char*) nameString + 1, nameString[0]); DBG ("name: "+ all); manufacturer = all.upToFirstOccurrenceOf (":", false, false).trim(); name = all.fromFirstOccurrenceOf (":", false, false).trim(); } if (infoString != 0 && infoString[0] != 0) { DBG ("info: " + String ((const char*) infoString + 1, infoString[0])); } if (name.isEmpty()) name = ""; } DisposeHandle (componentNameHandle); DisposeHandle (componentInfoHandle); } } bool getComponentDescFromIdentifier (const String& fileOrIdentifier, ComponentDescription& desc, String& name, String& version, String& manufacturer) { zerostruct (desc); if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) { String s (fileOrIdentifier.substring (jmax (fileOrIdentifier.lastIndexOfChar (':'), fileOrIdentifier.lastIndexOfChar ('/')) + 1)); StringArray tokens; tokens.addTokens (s, ",", String::empty); tokens.trim(); tokens.removeEmptyStrings(); if (tokens.size() == 3) { desc.componentType = stringToOSType (tokens[0]); desc.componentSubType = stringToOSType (tokens[1]); desc.componentManufacturer = stringToOSType (tokens[2]); ComponentRecord* comp = FindNextComponent (0, &desc); if (comp != 0) { getAUDetails (comp, name, manufacturer); return true; } } } return false; } } class AudioUnitPluginWindowCarbon; class AudioUnitPluginWindowCocoa; class AudioUnitPluginInstance : public AudioPluginInstance { public: ~AudioUnitPluginInstance(); void initialise(); // AudioPluginInstance methods: void fillInPluginDescription (PluginDescription& desc) const { desc.name = pluginName; desc.descriptiveName = pluginName; desc.fileOrIdentifier = AudioUnitFormatHelpers::createAUPluginIdentifier (componentDesc); desc.uid = ((int) componentDesc.componentType) ^ ((int) componentDesc.componentSubType) ^ ((int) componentDesc.componentManufacturer); desc.lastFileModTime = Time(); desc.pluginFormatName = "AudioUnit"; desc.category = getCategory(); desc.manufacturerName = manufacturer; desc.version = version; desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); } void* getPlatformSpecificData() { return audioUnit; } const String getName() const { return pluginName; } bool acceptsMidi() const { return wantsMidiMessages; } bool producesMidi() const { return false; } // AudioProcessor methods: void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void releaseResources(); void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); bool hasEditor() const; AudioProcessorEditor* createEditor(); const String getInputChannelName (int index) const; bool isInputChannelStereoPair (int index) const; const String getOutputChannelName (int index) const; bool isOutputChannelStereoPair (int index) const; int getNumParameters(); float getParameter (int index); void setParameter (int index, float newValue); const String getParameterName (int index); const String getParameterText (int index); bool isParameterAutomatable (int index) const; int getNumPrograms(); int getCurrentProgram(); void setCurrentProgram (int index); const String getProgramName (int index); void changeProgramName (int index, const String& newName); void getStateInformation (MemoryBlock& destData); void getCurrentProgramStateInformation (MemoryBlock& destData); void setStateInformation (const void* data, int sizeInBytes); void setCurrentProgramStateInformation (const void* data, int sizeInBytes); void refreshParameterListFromPlugin(); private: friend class AudioUnitPluginWindowCarbon; friend class AudioUnitPluginWindowCocoa; friend class AudioUnitPluginFormat; ComponentDescription componentDesc; String pluginName, manufacturer, version; String fileOrIdentifier; CriticalSection lock; bool wantsMidiMessages, wasPlaying, prepared; HeapBlock outputBufferList; AudioTimeStamp timeStamp; AudioSampleBuffer* currentBuffer; AudioUnit audioUnit; Array parameterIds; bool getComponentDescFromFile (const String& fileOrIdentifier); void setPluginCallbacks(); OSStatus renderGetInput (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) const; static OSStatus renderGetInputCallback (void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { return ((AudioUnitPluginInstance*) inRefCon) ->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); } OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const; OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const; OSStatus getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat); static OSStatus getBeatAndTempoCallback (void* inHostUserData, Float64* outCurrentBeat, Float64* outCurrentTempo) { return ((AudioUnitPluginInstance*) inHostUserData)->getBeatAndTempo (outCurrentBeat, outCurrentTempo); } static OSStatus getMusicalTimeLocationCallback (void* inHostUserData, UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) { return ((AudioUnitPluginInstance*) inHostUserData) ->getMusicalTimeLocation (outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, outTimeSig_Denominator, outCurrentMeasureDownBeat); } static OSStatus getTransportStateCallback (void* inHostUserData, Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat) { return ((AudioUnitPluginInstance*) inHostUserData) ->getTransportState (outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, outIsCycling, outCycleStartBeat, outCycleEndBeat); } void getNumChannels (int& numIns, int& numOuts) { numIns = 0; numOuts = 0; AUChannelInfo supportedChannels [128]; UInt32 supportedChannelsSize = sizeof (supportedChannels); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, supportedChannels, &supportedChannelsSize) == noErr && supportedChannelsSize > 0) { int explicitNumIns = 0; int explicitNumOuts = 0; int maximumNumIns = 0; int maximumNumOuts = 0; for (int i = 0; i < supportedChannelsSize / sizeof (AUChannelInfo); ++i) { const int inChannels = (int) supportedChannels[i].inChannels; const int outChannels = (int) supportedChannels[i].outChannels; if (inChannels < 0) maximumNumIns = jmin (maximumNumIns, inChannels); else explicitNumIns = jmax (explicitNumIns, inChannels); if (outChannels < 0) maximumNumOuts = jmin (maximumNumOuts, outChannels); else explicitNumOuts = jmax (explicitNumOuts, outChannels); } if ((maximumNumIns == -1 && maximumNumOuts == -1) // (special meaning: any number of ins/outs, as long as they match) || (maximumNumIns == -2 && maximumNumOuts == -1) // (special meaning: any number of ins/outs, even if they don't match) || (maximumNumIns == -1 && maximumNumOuts == -2)) { numIns = numOuts = 2; } else { numIns = explicitNumIns; numOuts = explicitNumOuts; if (maximumNumIns == -1 || (maximumNumIns < 0 && explicitNumIns <= -maximumNumIns)) numIns = 2; if (maximumNumOuts == -1 || (maximumNumOuts < 0 && explicitNumOuts <= -maximumNumOuts)) numOuts = 2; } } else { // (this really means the plugin will take any number of ins/outs as long // as they are the same) numIns = numOuts = 2; } } const String getCategory() const; AudioUnitPluginInstance (const String& fileOrIdentifier); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance); }; AudioUnitPluginInstance::AudioUnitPluginInstance (const String& fileOrIdentifier) : fileOrIdentifier (fileOrIdentifier), wantsMidiMessages (false), wasPlaying (false), prepared (false), currentBuffer (nullptr), audioUnit (0) { using namespace AudioUnitFormatHelpers; try { ++insideCallback; log ("Opening AU: " + fileOrIdentifier); if (getComponentDescFromFile (fileOrIdentifier)) { ComponentRecord* const comp = FindNextComponent (0, &componentDesc); if (comp != 0) { audioUnit = (AudioUnit) OpenComponent (comp); wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice || componentDesc.componentType == kAudioUnitType_MusicEffect; } } --insideCallback; } catch (...) { --insideCallback; } } AudioUnitPluginInstance::~AudioUnitPluginInstance() { const ScopedLock sl (lock); jassert (AudioUnitFormatHelpers::insideCallback == 0); if (audioUnit != 0) { AudioUnitUninitialize (audioUnit); CloseComponent (audioUnit); audioUnit = 0; } } bool AudioUnitPluginInstance::getComponentDescFromFile (const String& fileOrIdentifier) { zerostruct (componentDesc); if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) return true; const File file (fileOrIdentifier); if (! file.hasFileExtension (".component")) return false; const char* const utf8 = fileOrIdentifier.toUTF8(); CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, strlen (utf8), file.isDirectory()); if (url != 0) { CFBundleRef bundleRef = CFBundleCreate (kCFAllocatorDefault, url); CFRelease (url); if (bundleRef != 0) { CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); if (name != 0 && CFGetTypeID (name) == CFStringGetTypeID()) pluginName = PlatformUtilities::cfStringToJuceString ((CFStringRef) name); if (pluginName.isEmpty()) pluginName = file.getFileNameWithoutExtension(); CFTypeRef versionString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleVersion")); if (versionString != 0 && CFGetTypeID (versionString) == CFStringGetTypeID()) version = PlatformUtilities::cfStringToJuceString ((CFStringRef) versionString); CFTypeRef manuString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleGetInfoString")); if (manuString != 0 && CFGetTypeID (manuString) == CFStringGetTypeID()) manufacturer = PlatformUtilities::cfStringToJuceString ((CFStringRef) manuString); short resFileId = CFBundleOpenBundleResourceMap (bundleRef); UseResFile (resFileId); for (int i = 1; i <= Count1Resources ('thng'); ++i) { Handle h = Get1IndResource ('thng', i); if (h != 0) { HLock (h); const uint32* const types = (const uint32*) *h; if (types[0] == kAudioUnitType_MusicDevice || types[0] == kAudioUnitType_MusicEffect || types[0] == kAudioUnitType_Effect || types[0] == kAudioUnitType_Generator || types[0] == kAudioUnitType_Panner) { componentDesc.componentType = types[0]; componentDesc.componentSubType = types[1]; componentDesc.componentManufacturer = types[2]; break; } HUnlock (h); ReleaseResource (h); } } CFBundleCloseBundleResourceMap (bundleRef, resFileId); CFRelease (bundleRef); } } return componentDesc.componentType != 0 && componentDesc.componentSubType != 0; } void AudioUnitPluginInstance::initialise() { refreshParameterListFromPlugin(); setPluginCallbacks(); int numIns, numOuts; getNumChannels (numIns, numOuts); setPlayConfigDetails (numIns, numOuts, 0, 0); setLatencySamples (0); } void AudioUnitPluginInstance::refreshParameterListFromPlugin() { parameterIds.clear(); if (audioUnit != 0) { UInt32 paramListSize = 0; AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, 0, ¶mListSize); if (paramListSize > 0) { parameterIds.insertMultiple (0, 0, paramListSize / sizeof (int)); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, parameterIds.getRawDataPointer(), ¶mListSize); } } } void AudioUnitPluginInstance::setPluginCallbacks() { if (audioUnit != 0) { { AURenderCallbackStruct info = { 0 }; info.inputProcRefCon = this; info.inputProc = renderGetInputCallback; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &info, sizeof (info)); } { HostCallbackInfo info = { 0 }; info.hostUserData = this; info.beatAndTempoProc = getBeatAndTempoCallback; info.musicalTimeLocationProc = getMusicalTimeLocationCallback; info.transportStateProc = getTransportStateCallback; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &info, sizeof (info)); } } } void AudioUnitPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) { if (audioUnit != 0) { releaseResources(); Float64 sampleRateIn = 0, sampleRateOut = 0; UInt32 sampleRateSize = sizeof (sampleRateIn); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRateIn, &sampleRateSize); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRateOut, &sampleRateSize); if (sampleRateIn != sampleRate_ || sampleRateOut != sampleRate_) { Float64 sr = sampleRate_; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sr, sizeof (Float64)); AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sr, sizeof (Float64)); } int numIns, numOuts; getNumChannels (numIns, numOuts); setPlayConfigDetails (numIns, numOuts, sampleRate_, samplesPerBlockExpected); Float64 latencySecs = 0.0; UInt32 latencySize = sizeof (latencySecs); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &latencySecs, &latencySize); setLatencySamples (roundToInt (latencySecs * sampleRate_)); AudioUnitReset (audioUnit, kAudioUnitScope_Input, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Output, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); { AudioStreamBasicDescription stream = { 0 }; stream.mSampleRate = sampleRate_; stream.mFormatID = kAudioFormatLinearPCM; stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; stream.mFramesPerPacket = 1; stream.mBytesPerPacket = 4; stream.mBytesPerFrame = 4; stream.mBitsPerChannel = 32; stream.mChannelsPerFrame = numIns; OSStatus err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stream, sizeof (stream)); stream.mChannelsPerFrame = numOuts; err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &stream, sizeof (stream)); } outputBufferList.calloc (sizeof (AudioBufferList) + sizeof (AudioBuffer) * (numOuts + 1), 1); outputBufferList->mNumberBuffers = numOuts; for (int i = numOuts; --i >= 0;) outputBufferList->mBuffers[i].mNumberChannels = 1; zerostruct (timeStamp); timeStamp.mSampleTime = 0; timeStamp.mHostTime = AudioGetCurrentHostTime(); timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; currentBuffer = 0; wasPlaying = false; prepared = (AudioUnitInitialize (audioUnit) == noErr); } } void AudioUnitPluginInstance::releaseResources() { if (prepared) { AudioUnitUninitialize (audioUnit); AudioUnitReset (audioUnit, kAudioUnitScope_Input, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Output, 0); AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); outputBufferList.free(); currentBuffer = 0; prepared = false; } } OSStatus AudioUnitPluginInstance::renderGetInput (AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) const { if (inBusNumber == 0 && currentBuffer != 0) { jassert (inNumberFrames == currentBuffer->getNumSamples()); // if this ever happens, might need to add extra handling for (int i = 0; i < ioData->mNumberBuffers; ++i) { if (i < currentBuffer->getNumChannels()) { memcpy (ioData->mBuffers[i].mData, currentBuffer->getSampleData (i, 0), sizeof (float) * inNumberFrames); } else { zeromem (ioData->mBuffers[i].mData, sizeof (float) * inNumberFrames); } } } return noErr; } void AudioUnitPluginInstance::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); if (prepared) { AudioUnitRenderActionFlags flags = 0; timeStamp.mHostTime = AudioGetCurrentHostTime(); for (int i = getNumOutputChannels(); --i >= 0;) { outputBufferList->mBuffers[i].mDataByteSize = sizeof (float) * numSamples; outputBufferList->mBuffers[i].mData = buffer.getSampleData (i, 0); } currentBuffer = &buffer; if (wantsMidiMessages) { const uint8* midiEventData; int midiEventSize, midiEventPosition; MidiBuffer::Iterator i (midiMessages); while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) { if (midiEventSize <= 3) MusicDeviceMIDIEvent (audioUnit, midiEventData[0], midiEventData[1], midiEventData[2], midiEventPosition); else MusicDeviceSysEx (audioUnit, midiEventData, midiEventSize); } midiMessages.clear(); } AudioUnitRender (audioUnit, &flags, &timeStamp, 0, numSamples, outputBufferList); timeStamp.mSampleTime += numSamples; } else { // Plugin not working correctly, so just bypass.. for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) buffer.clear (i, 0, buffer.getNumSamples()); } } OSStatus AudioUnitPluginInstance::getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != nullptr && ph->getCurrentPosition (result)) { if (outCurrentBeat != nullptr) *outCurrentBeat = result.ppqPosition; if (outCurrentTempo != nullptr) *outCurrentTempo = result.bpm; } else { if (outCurrentBeat != nullptr) *outCurrentBeat = 0; if (outCurrentTempo != nullptr) *outCurrentTempo = 120.0; } return noErr; } OSStatus AudioUnitPluginInstance::getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != nullptr && ph->getCurrentPosition (result)) { if (outTimeSig_Numerator != nullptr) *outTimeSig_Numerator = result.timeSigNumerator; if (outTimeSig_Denominator != nullptr) *outTimeSig_Denominator = result.timeSigDenominator; if (outDeltaSampleOffsetToNextBeat != nullptr) *outDeltaSampleOffsetToNextBeat = 0; //xxx if (outCurrentMeasureDownBeat != nullptr) *outCurrentMeasureDownBeat = result.ppqPositionOfLastBarStart; //xxx wrong } else { if (outDeltaSampleOffsetToNextBeat != nullptr) *outDeltaSampleOffsetToNextBeat = 0; if (outTimeSig_Numerator != nullptr) *outTimeSig_Numerator = 4; if (outTimeSig_Denominator != nullptr) *outTimeSig_Denominator = 4; if (outCurrentMeasureDownBeat != nullptr) *outCurrentMeasureDownBeat = 0; } return noErr; } OSStatus AudioUnitPluginInstance::getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat) { AudioPlayHead* const ph = getPlayHead(); AudioPlayHead::CurrentPositionInfo result; if (ph != nullptr && ph->getCurrentPosition (result)) { if (outIsPlaying != nullptr) *outIsPlaying = result.isPlaying; if (outTransportStateChanged != nullptr) { *outTransportStateChanged = result.isPlaying != wasPlaying; wasPlaying = result.isPlaying; } if (outCurrentSampleInTimeLine != nullptr) *outCurrentSampleInTimeLine = roundToInt (result.timeInSeconds * getSampleRate()); if (outIsCycling != nullptr) *outIsCycling = false; if (outCycleStartBeat != nullptr) *outCycleStartBeat = 0; if (outCycleEndBeat != nullptr) *outCycleEndBeat = 0; } else { if (outIsPlaying != nullptr) *outIsPlaying = false; if (outTransportStateChanged != nullptr) *outTransportStateChanged = false; if (outCurrentSampleInTimeLine != nullptr) *outCurrentSampleInTimeLine = 0; if (outIsCycling != nullptr) *outIsCycling = false; if (outCycleStartBeat != nullptr) *outCycleStartBeat = 0; if (outCycleEndBeat != nullptr) *outCycleEndBeat = 0; } return noErr; } class AudioUnitPluginWindowCocoa : public AudioProcessorEditor, public Timer { public: AudioUnitPluginWindowCocoa (AudioUnitPluginInstance& plugin_, const bool createGenericViewIfNeeded) : AudioProcessorEditor (&plugin_), plugin (plugin_) { addAndMakeVisible (&wrapper); setOpaque (true); setVisible (true); setSize (100, 100); createView (createGenericViewIfNeeded); } ~AudioUnitPluginWindowCocoa() { const bool wasValid = isValid(); wrapper.setView (0); if (wasValid) plugin.editorBeingDeleted (this); } bool isValid() const { return wrapper.getView() != 0; } void paint (Graphics& g) { g.fillAll (Colours::white); } void resized() { wrapper.setSize (getWidth(), getHeight()); } void timerCallback() { wrapper.resizeToFitView(); startTimer (jmin (713, getTimerInterval() + 51)); } void childBoundsChanged (Component* child) { setSize (wrapper.getWidth(), wrapper.getHeight()); startTimer (70); } private: AudioUnitPluginInstance& plugin; NSViewComponent wrapper; bool createView (const bool createGenericViewIfNeeded) { NSView* pluginView = nil; UInt32 dataSize = 0; Boolean isWritable = false; AudioUnitInitialize (plugin.audioUnit); if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize != 0 && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr) { HeapBlock info; info.calloc (dataSize, 1); if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, info, &dataSize) == noErr) { NSString* viewClassName = (NSString*) (info->mCocoaAUViewClass[0]); NSString* path = (NSString*) CFURLCopyPath (info->mCocoaAUViewBundleLocation); NSBundle* viewBundle = [NSBundle bundleWithPath: [path autorelease]]; Class viewClass = [viewBundle classNamed: viewClassName]; if ([viewClass conformsToProtocol: @protocol (AUCocoaUIBase)] && [viewClass instancesRespondToSelector: @selector (interfaceVersion)] && [viewClass instancesRespondToSelector: @selector (uiViewForAudioUnit: withSize:)]) { id factory = [[[viewClass alloc] init] autorelease]; pluginView = [factory uiViewForAudioUnit: plugin.audioUnit withSize: NSMakeSize (getWidth(), getHeight())]; } for (int i = (dataSize - sizeof (CFURLRef)) / sizeof (CFStringRef); --i >= 0;) CFRelease (info->mCocoaAUViewClass[i]); CFRelease (info->mCocoaAUViewBundleLocation); } } if (createGenericViewIfNeeded && (pluginView == 0)) pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.audioUnit]; wrapper.setView (pluginView); if (pluginView != nil) { timerCallback(); startTimer (70); } return pluginView != nil; } }; #if JUCE_SUPPORT_CARBON class AudioUnitPluginWindowCarbon : public AudioProcessorEditor { public: AudioUnitPluginWindowCarbon (AudioUnitPluginInstance& plugin_) : AudioProcessorEditor (&plugin_), plugin (plugin_), viewComponent (0) { addAndMakeVisible (innerWrapper = new InnerWrapperComponent (this)); setOpaque (true); setVisible (true); setSize (400, 300); ComponentDescription viewList [16]; UInt32 viewListSize = sizeof (viewList); AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &viewList, &viewListSize); componentRecord = FindNextComponent (0, &viewList[0]); } ~AudioUnitPluginWindowCarbon() { innerWrapper = 0; if (isValid()) plugin.editorBeingDeleted (this); } bool isValid() const noexcept { return componentRecord != 0; } void paint (Graphics& g) { g.fillAll (Colours::black); } void resized() { innerWrapper->setSize (getWidth(), getHeight()); } bool keyStateChanged (bool) { return false; } bool keyPressed (const KeyPress&) { return false; } AudioUnit getAudioUnit() const { return plugin.audioUnit; } AudioUnitCarbonView getViewComponent() { if (viewComponent == 0 && componentRecord != 0) viewComponent = (AudioUnitCarbonView) OpenComponent (componentRecord); return viewComponent; } void closeViewComponent() { if (viewComponent != 0) { log ("Closing AU GUI: " + plugin.getName()); CloseComponent (viewComponent); viewComponent = 0; } } private: AudioUnitPluginInstance& plugin; ComponentRecord* componentRecord; AudioUnitCarbonView viewComponent; class InnerWrapperComponent : public CarbonViewWrapperComponent { public: InnerWrapperComponent (AudioUnitPluginWindowCarbon* const owner_) : owner (owner_) { } ~InnerWrapperComponent() { deleteWindow(); } HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) { log ("Opening AU GUI: " + owner->plugin.getName()); AudioUnitCarbonView viewComponent = owner->getViewComponent(); if (viewComponent == 0) return 0; Float32Point pos = { 0, 0 }; Float32Point size = { 250, 200 }; HIViewRef pluginView = 0; AudioUnitCarbonViewCreate (viewComponent, owner->getAudioUnit(), windowRef, rootView, &pos, &size, (ControlRef*) &pluginView); return pluginView; } void removeView (HIViewRef) { owner->closeViewComponent(); } private: AudioUnitPluginWindowCarbon* const owner; }; friend class InnerWrapperComponent; ScopedPointer innerWrapper; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginWindowCarbon); }; #endif bool AudioUnitPluginInstance::hasEditor() const { return true; } AudioProcessorEditor* AudioUnitPluginInstance::createEditor() { ScopedPointer w (new AudioUnitPluginWindowCocoa (*this, false)); if (! static_cast (static_cast (w))->isValid()) w = 0; #if JUCE_SUPPORT_CARBON if (w == nullptr) { w = new AudioUnitPluginWindowCarbon (*this); if (! static_cast (static_cast (w))->isValid()) w = 0; } #endif if (w == nullptr) w = new AudioUnitPluginWindowCocoa (*this, true); // use AUGenericView as a fallback return w.release(); } const String AudioUnitPluginInstance::getCategory() const { const char* result = nullptr; switch (componentDesc.componentType) { case kAudioUnitType_Effect: case kAudioUnitType_MusicEffect: result = "Effect"; break; case kAudioUnitType_MusicDevice: result = "Synth"; break; case kAudioUnitType_Generator: result = "Generator"; break; case kAudioUnitType_Panner: result = "Panner"; break; default: break; } return result; } int AudioUnitPluginInstance::getNumParameters() { return parameterIds.size(); } float AudioUnitPluginInstance::getParameter (int index) { const ScopedLock sl (lock); Float32 value = 0.0f; if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitGetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), kAudioUnitScope_Global, 0, &value); } return value; } void AudioUnitPluginInstance::setParameter (int index, float newValue) { const ScopedLock sl (lock); if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitSetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), kAudioUnitScope_Global, 0, newValue, 0); } } const String AudioUnitPluginInstance::getParameterName (int index) { AudioUnitParameterInfo info = { 0 }; UInt32 sz = sizeof (info); String name; if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameterIds [index], &info, &sz) == noErr) { if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) name = PlatformUtilities::cfStringToJuceString (info.cfNameString); else name = String (info.name, sizeof (info.name)); } return name; } const String AudioUnitPluginInstance::getParameterText (int index) { return String (getParameter (index)); } bool AudioUnitPluginInstance::isParameterAutomatable (int index) const { AudioUnitParameterInfo info; UInt32 sz = sizeof (info); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, parameterIds [index], &info, &sz) == noErr) { return (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; } return true; } int AudioUnitPluginInstance::getNumPrograms() { CFArrayRef presets; UInt32 sz = sizeof (CFArrayRef); int num = 0; if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &presets, &sz) == noErr) { num = (int) CFArrayGetCount (presets); CFRelease (presets); } return num; } int AudioUnitPluginInstance::getCurrentProgram() { AUPreset current; current.presetNumber = 0; UInt32 sz = sizeof (AUPreset); AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, ¤t, &sz); return current.presetNumber; } void AudioUnitPluginInstance::setCurrentProgram (int newIndex) { AUPreset current; current.presetNumber = newIndex; current.presetName = 0; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0, ¤t, sizeof (AUPreset)); } const String AudioUnitPluginInstance::getProgramName (int index) { String s; CFArrayRef presets; UInt32 sz = sizeof (CFArrayRef); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &presets, &sz) == noErr) { for (CFIndex i = 0; i < CFArrayGetCount (presets); ++i) { const AUPreset* p = (const AUPreset*) CFArrayGetValueAtIndex (presets, i); if (p != nullptr && p->presetNumber == index) { s = PlatformUtilities::cfStringToJuceString (p->presetName); break; } } CFRelease (presets); } return s; } void AudioUnitPluginInstance::changeProgramName (int index, const String& newName) { jassertfalse; // xxx not implemented! } const String AudioUnitPluginInstance::getInputChannelName (int index) const { if (isPositiveAndBelow (index, getNumInputChannels())) return "Input " + String (index + 1); return String::empty; } bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const { if (! isPositiveAndBelow (index, getNumInputChannels())) return false; return true; } const String AudioUnitPluginInstance::getOutputChannelName (int index) const { if (isPositiveAndBelow (index, getNumOutputChannels())) return "Output " + String (index + 1); return String::empty; } bool AudioUnitPluginInstance::isOutputChannelStereoPair (int index) const { if (! isPositiveAndBelow (index, getNumOutputChannels())) return false; return true; } void AudioUnitPluginInstance::getStateInformation (MemoryBlock& destData) { getCurrentProgramStateInformation (destData); } void AudioUnitPluginInstance::getCurrentProgramStateInformation (MemoryBlock& destData) { CFPropertyListRef propertyList = 0; UInt32 sz = sizeof (CFPropertyListRef); if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &propertyList, &sz) == noErr) { CFWriteStreamRef stream = CFWriteStreamCreateWithAllocatedBuffers (kCFAllocatorDefault, kCFAllocatorDefault); CFWriteStreamOpen (stream); CFIndex bytesWritten = CFPropertyListWriteToStream (propertyList, stream, kCFPropertyListBinaryFormat_v1_0, 0); CFWriteStreamClose (stream); CFDataRef data = (CFDataRef) CFWriteStreamCopyProperty (stream, kCFStreamPropertyDataWritten); destData.setSize (bytesWritten); destData.copyFrom (CFDataGetBytePtr (data), 0, destData.getSize()); CFRelease (data); CFRelease (stream); CFRelease (propertyList); } } void AudioUnitPluginInstance::setStateInformation (const void* data, int sizeInBytes) { setCurrentProgramStateInformation (data, sizeInBytes); } void AudioUnitPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { CFReadStreamRef stream = CFReadStreamCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, sizeInBytes, kCFAllocatorNull); CFReadStreamOpen (stream); CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0; CFPropertyListRef propertyList = CFPropertyListCreateFromStream (kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, &format, 0); CFRelease (stream); if (propertyList != 0) AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &propertyList, sizeof (propertyList)); } AudioUnitPluginFormat::AudioUnitPluginFormat() { } AudioUnitPluginFormat::~AudioUnitPluginFormat() { } void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier) { if (! fileMightContainThisPluginType (fileOrIdentifier)) return; PluginDescription desc; desc.fileOrIdentifier = fileOrIdentifier; desc.uid = 0; try { ScopedPointer createdInstance (createInstanceFromDescription (desc)); AudioUnitPluginInstance* const auInstance = dynamic_cast ((AudioPluginInstance*) createdInstance); if (auInstance != nullptr) { auInstance->fillInPluginDescription (desc); results.add (new PluginDescription (desc)); } } catch (...) { // crashed while loading... } } AudioPluginInstance* AudioUnitPluginFormat::createInstanceFromDescription (const PluginDescription& desc) { if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { ScopedPointer result (new AudioUnitPluginInstance (desc.fileOrIdentifier)); if (result->audioUnit != 0) { result->initialise(); return result.release(); } } return nullptr; } const StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath& /*directoriesToSearch*/, const bool /*recursive*/) { StringArray result; ComponentRecord* comp = 0; for (;;) { ComponentDescription desc = { 0 }; comp = FindNextComponent (comp, &desc); if (comp == 0) break; GetComponentInfo (comp, &desc, 0, 0, 0); if (desc.componentType == kAudioUnitType_MusicDevice || desc.componentType == kAudioUnitType_MusicEffect || desc.componentType == kAudioUnitType_Effect || desc.componentType == kAudioUnitType_Generator || desc.componentType == kAudioUnitType_Panner) { const String s (AudioUnitFormatHelpers::createAUPluginIdentifier (desc)); DBG (s); result.add (s); } } return result; } bool AudioUnitPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) { ComponentDescription desc; String name, version, manufacturer; if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer)) return FindNextComponent (0, &desc) != 0; const File f (fileOrIdentifier); return f.hasFileExtension (".component") && f.isDirectory(); } const String AudioUnitPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { ComponentDescription desc; String name, version, manufacturer; AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer); if (name.isEmpty()) name = fileOrIdentifier; return name; } bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) { if (desc.fileOrIdentifier.startsWithIgnoreCase (AudioUnitFormatHelpers::auIdentifierPrefix)) return fileMightContainThisPluginType (desc.fileOrIdentifier); else return File (desc.fileOrIdentifier).exists(); } const FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() { return FileSearchPath ("/(Default AudioUnit locations)"); } #endif END_JUCE_NAMESPACE #undef log #endif /*** End of inlined file: juce_AudioUnitPluginFormat.mm ***/ /*** Start of inlined file: juce_VSTPluginFormat.mm ***/ // This file just wraps juce_VSTPluginFormat.cpp in an objective-C wrapper #define JUCE_MAC_VST_INCLUDED 1 /*** Start of inlined file: juce_VSTPluginFormat.cpp ***/ #if JUCE_PLUGINHOST_VST && (JUCE_MAC_VST_INCLUDED || ! JUCE_MAC) #if JUCE_WINDOWS #undef _WIN32_WINNT #define _WIN32_WINNT 0x500 #undef STRICT #define STRICT #include #include #pragma warning (disable : 4312 4355) #ifdef __INTEL_COMPILER #pragma warning (disable : 1899) #endif #elif JUCE_LINUX #include #include #include #include #include #undef Font #undef KeyPress #undef Drawable #undef Time #else #include #include #endif #if ! (JUCE_MAC && JUCE_64BIT) BEGIN_JUCE_NAMESPACE #if JUCE_MAC && JUCE_SUPPORT_CARBON #endif #undef PRAGMA_ALIGN_SUPPORTED #define VST_FORCE_DEPRECATED 0 #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4996) #endif /* Obviously you're going to need the Steinberg vstsdk2.4 folder in your include path if you want to add VST support. If you're not interested in VSTs, you can disable them by changing the JUCE_PLUGINHOST_VST flag in juce_Config.h */ #include #if JUCE_MSVC #pragma warning (pop) #endif #if JUCE_LINUX #define Font JUCE_NAMESPACE::Font #define KeyPress JUCE_NAMESPACE::KeyPress #define Drawable JUCE_NAMESPACE::Drawable #define Time JUCE_NAMESPACE::Time #endif /*** Start of inlined file: juce_VSTMidiEventList.h ***/ #ifdef __aeffect__ #ifndef __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ #define __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ /** Holds a set of VSTMidiEvent objects and makes it easy to add events to the list. This is used by both the VST hosting code and the plugin wrapper. */ class VSTMidiEventList { public: VSTMidiEventList() : numEventsUsed (0), numEventsAllocated (0) { } ~VSTMidiEventList() { freeEvents(); } void clear() { numEventsUsed = 0; if (events != nullptr) events->numEvents = 0; } void addEvent (const void* const midiData, const int numBytes, const int frameOffset) { ensureSize (numEventsUsed + 1); VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); events->numEvents = ++numEventsUsed; if (numBytes <= 4) { if (e->type == kVstSysExType) { delete[] (((VstMidiSysexEvent*) e)->sysexDump); e->type = kVstMidiType; e->byteSize = sizeof (VstMidiEvent); e->noteLength = 0; e->noteOffset = 0; e->detune = 0; e->noteOffVelocity = 0; } e->deltaFrames = frameOffset; memcpy (e->midiData, midiData, numBytes); } else { VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; if (se->type == kVstSysExType) delete[] se->sysexDump; se->sysexDump = new char [numBytes]; memcpy (se->sysexDump, midiData, numBytes); se->type = kVstSysExType; se->byteSize = sizeof (VstMidiSysexEvent); se->deltaFrames = frameOffset; se->flags = 0; se->dumpBytes = numBytes; se->resvd1 = 0; se->resvd2 = 0; } } // Handy method to pull the events out of an event buffer supplied by the host // or plugin. static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) { for (int i = 0; i < events->numEvents; ++i) { const VstEvent* const e = events->events[i]; if (e != nullptr) { if (e->type == kVstMidiType) { dest.addEvent ((const JUCE_NAMESPACE::uint8*) ((const VstMidiEvent*) e)->midiData, 4, e->deltaFrames); } else if (e->type == kVstSysExType) { dest.addEvent ((const JUCE_NAMESPACE::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, (int) ((const VstMidiSysexEvent*) e)->dumpBytes, e->deltaFrames); } } } } void ensureSize (int numEventsNeeded) { if (numEventsNeeded > numEventsAllocated) { numEventsNeeded = (numEventsNeeded + 32) & ~31; const int size = 20 + sizeof (VstEvent*) * numEventsNeeded; if (events == nullptr) events.calloc (size, 1); else events.realloc (size, 1); for (int i = numEventsAllocated; i < numEventsNeeded; ++i) events->events[i] = allocateVSTEvent(); numEventsAllocated = numEventsNeeded; } } void freeEvents() { if (events != nullptr) { for (int i = numEventsAllocated; --i >= 0;) freeVSTEvent (events->events[i]); events.free(); numEventsUsed = 0; numEventsAllocated = 0; } } HeapBlock events; private: int numEventsUsed, numEventsAllocated; static VstEvent* allocateVSTEvent() { VstEvent* const e = (VstEvent*) ::calloc (1, sizeof (VstMidiEvent) > sizeof (VstMidiSysexEvent) ? sizeof (VstMidiEvent) : sizeof (VstMidiSysexEvent)); e->type = kVstMidiType; e->byteSize = sizeof (VstMidiEvent); return e; } static void freeVSTEvent (VstEvent* e) { if (e->type == kVstSysExType) delete[] (((VstMidiSysexEvent*) e)->sysexDump); ::free (e); } }; #endif // __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ #endif // __JUCE_VSTMIDIEVENTLIST_JUCEHEADER__ /*** End of inlined file: juce_VSTMidiEventList.h ***/ #if ! JUCE_WINDOWS static void _fpreset() {} static void _clearfp() {} #endif extern void juce_callAnyTimersSynchronously(); const int fxbVersionNum = 1; struct fxProgram { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCk' long version; long fxID; // fx unique id long fxVersion; long numParams; char prgName[28]; float params[1]; // variable no. of parameters }; struct fxSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxBk' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char future[128]; fxProgram programs[1]; // variable no. of programs }; struct fxChunkSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char future[128]; long chunkSize; char chunk[8]; // variable }; struct fxProgramSet { long chunkMagic; // 'CcnK' long byteSize; // of this chunk, excl. magic + byteSize long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' long version; long fxID; // fx unique id long fxVersion; long numPrograms; char name[28]; long chunkSize; char chunk[8]; // variable }; namespace { long vst_swap (const long x) noexcept { #ifdef JUCE_LITTLE_ENDIAN return (long) ByteOrder::swap ((uint32) x); #else return x; #endif } float vst_swapFloat (const float x) noexcept { #ifdef JUCE_LITTLE_ENDIAN union { uint32 asInt; float asFloat; } n; n.asFloat = x; n.asInt = ByteOrder::swap (n.asInt); return n.asFloat; #else return x; #endif } double getVSTHostTimeNanoseconds() { #if JUCE_WINDOWS return timeGetTime() * 1000000.0; #elif JUCE_LINUX timeval micro; gettimeofday (µ, 0); return micro.tv_usec * 1000.0; #elif JUCE_MAC UnsignedWide micro; Microseconds (µ); return micro.lo * 1000.0; #endif } } typedef AEffect* (VSTCALLBACK *MainCall) (audioMasterCallback); static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); static int shellUIDToCreate = 0; static int insideVSTCallback = 0; class IdleCallRecursionPreventer { public: IdleCallRecursionPreventer() : isMessageThread (MessageManager::getInstance()->isThisTheMessageThread()) { if (isMessageThread) ++insideVSTCallback; } ~IdleCallRecursionPreventer() { if (isMessageThread) --insideVSTCallback; } private: const bool isMessageThread; JUCE_DECLARE_NON_COPYABLE (IdleCallRecursionPreventer); }; class VSTPluginWindow; // Change this to disable logging of various VST activities #ifndef VST_LOGGING #define VST_LOGGING 1 #endif #if VST_LOGGING #define log(a) Logger::writeToLog(a); #else #define log(a) #endif #if JUCE_MAC && JUCE_PPC static void* NewCFMFromMachO (void* const machofp) noexcept { void* result = (void*) new char[8]; ((void**) result)[0] = machofp; ((void**) result)[1] = result; return result; } #endif #if JUCE_LINUX extern Display* display; extern XContext windowHandleXContext; typedef void (*EventProcPtr) (XEvent* ev); static bool xErrorTriggered; namespace { int temporaryErrorHandler (Display*, XErrorEvent*) { xErrorTriggered = true; return 0; } int getPropertyFromXWindow (Window handle, Atom atom) { XErrorHandler oldErrorHandler = XSetErrorHandler (temporaryErrorHandler); xErrorTriggered = false; int userSize; unsigned long bytes, userCount; unsigned char* data; Atom userType; XGetWindowProperty (display, handle, atom, 0, 1, false, AnyPropertyType, &userType, &userSize, &userCount, &bytes, &data); XSetErrorHandler (oldErrorHandler); return (userCount == 1 && ! xErrorTriggered) ? *reinterpret_cast (data) : 0; } Window getChildWindow (Window windowToCheck) { Window rootWindow, parentWindow; Window* childWindows; unsigned int numChildren; XQueryTree (display, windowToCheck, &rootWindow, &parentWindow, &childWindows, &numChildren); if (numChildren > 0) return childWindows [0]; return 0; } void translateJuceToXButtonModifiers (const MouseEvent& e, XEvent& ev) noexcept { if (e.mods.isLeftButtonDown()) { ev.xbutton.button = Button1; ev.xbutton.state |= Button1Mask; } else if (e.mods.isRightButtonDown()) { ev.xbutton.button = Button3; ev.xbutton.state |= Button3Mask; } else if (e.mods.isMiddleButtonDown()) { ev.xbutton.button = Button2; ev.xbutton.state |= Button2Mask; } } void translateJuceToXMotionModifiers (const MouseEvent& e, XEvent& ev) noexcept { if (e.mods.isLeftButtonDown()) ev.xmotion.state |= Button1Mask; else if (e.mods.isRightButtonDown()) ev.xmotion.state |= Button3Mask; else if (e.mods.isMiddleButtonDown()) ev.xmotion.state |= Button2Mask; } void translateJuceToXCrossingModifiers (const MouseEvent& e, XEvent& ev) noexcept { if (e.mods.isLeftButtonDown()) ev.xcrossing.state |= Button1Mask; else if (e.mods.isRightButtonDown()) ev.xcrossing.state |= Button3Mask; else if (e.mods.isMiddleButtonDown()) ev.xcrossing.state |= Button2Mask; } void translateJuceToXMouseWheelModifiers (const MouseEvent& e, const float increment, XEvent& ev) noexcept { if (increment < 0) { ev.xbutton.button = Button5; ev.xbutton.state |= Button5Mask; } else if (increment > 0) { ev.xbutton.button = Button4; ev.xbutton.state |= Button4Mask; } } } #endif class ModuleHandle : public ReferenceCountedObject { public: File file; MainCall moduleMain; String pluginName; static Array & getActiveModules() { static Array activeModules; return activeModules; } static ModuleHandle* findOrCreateModule (const File& file) { for (int i = getActiveModules().size(); --i >= 0;) { ModuleHandle* const module = getActiveModules().getUnchecked(i); if (module->file == file) return module; } _fpreset(); // (doesn't do any harm) const IdleCallRecursionPreventer icrp; shellUIDToCreate = 0; log ("Attempting to load VST: " + file.getFullPathName()); ScopedPointer m (new ModuleHandle (file)); if (! m->open()) m = nullptr; _fpreset(); // (doesn't do any harm) return m.release(); } ModuleHandle (const File& file_) : file (file_), moduleMain (0), #if JUCE_WINDOWS || JUCE_LINUX hModule (0) #elif JUCE_MAC fragId (0), resHandle (0), bundleRef (0), resFileId (0) #endif { getActiveModules().add (this); #if JUCE_WINDOWS || JUCE_LINUX fullParentDirectoryPathName = file_.getParentDirectory().getFullPathName(); #elif JUCE_MAC FSRef ref; PlatformUtilities::makeFSRefFromPath (&ref, file_.getParentDirectory().getFullPathName()); FSGetCatalogInfo (&ref, kFSCatInfoNone, 0, 0, &parentDirFSSpec, 0); #endif } ~ModuleHandle() { getActiveModules().removeValue (this); close(); } #if JUCE_WINDOWS || JUCE_LINUX void* hModule; String fullParentDirectoryPathName; bool open() { #if JUCE_WINDOWS static bool timePeriodSet = false; if (! timePeriodSet) { timePeriodSet = true; timeBeginPeriod (2); } #endif pluginName = file.getFileNameWithoutExtension(); hModule = PlatformUtilities::loadDynamicLibrary (file.getFullPathName()); moduleMain = (MainCall) PlatformUtilities::getProcedureEntryPoint (hModule, "VSTPluginMain"); if (moduleMain == 0) moduleMain = (MainCall) PlatformUtilities::getProcedureEntryPoint (hModule, "main"); return moduleMain != 0; } void close() { _fpreset(); // (doesn't do any harm) PlatformUtilities::freeDynamicLibrary (hModule); } void closeEffect (AEffect* eff) { eff->dispatcher (eff, effClose, 0, 0, 0, 0); } #else CFragConnectionID fragId; Handle resHandle; CFBundleRef bundleRef; FSSpec parentDirFSSpec; short resFileId; bool open() { bool ok = false; const String filename (file.getFullPathName()); if (file.hasFileExtension (".vst")) { const char* const utf8 = filename.toUTF8().getAddress(); CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, strlen (utf8), file.isDirectory()); if (url != 0) { bundleRef = CFBundleCreate (kCFAllocatorDefault, url); CFRelease (url); if (bundleRef != 0) { if (CFBundleLoadExecutable (bundleRef)) { moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("main_macho")); if (moduleMain == 0) moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("VSTPluginMain")); if (moduleMain != 0) { CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); if (name != 0) { if (CFGetTypeID (name) == CFStringGetTypeID()) { char buffer[1024]; if (CFStringGetCString ((CFStringRef) name, buffer, sizeof (buffer), CFStringGetSystemEncoding())) pluginName = buffer; } } if (pluginName.isEmpty()) pluginName = file.getFileNameWithoutExtension(); resFileId = CFBundleOpenBundleResourceMap (bundleRef); ok = true; } } if (! ok) { CFBundleUnloadExecutable (bundleRef); CFRelease (bundleRef); bundleRef = 0; } } } } #if JUCE_PPC else { FSRef fn; if (FSPathMakeRef ((UInt8*) filename.toUTF8().getAddress(), &fn, 0) == noErr) { resFileId = FSOpenResFile (&fn, fsRdPerm); if (resFileId != -1) { const int numEffs = Count1Resources ('aEff'); for (int i = 0; i < numEffs; ++i) { resHandle = Get1IndResource ('aEff', i + 1); if (resHandle != 0) { OSType type; Str255 name; SInt16 id; GetResInfo (resHandle, &id, &type, name); pluginName = String ((const char*) name + 1, name[0]); DetachResource (resHandle); HLock (resHandle); Ptr ptr; Str255 errorText; OSErr err = GetMemFragment (*resHandle, GetHandleSize (resHandle), name, kPrivateCFragCopy, &fragId, &ptr, errorText); if (err == noErr) { moduleMain = (MainCall) newMachOFromCFM (ptr); ok = true; } else { HUnlock (resHandle); } break; } } if (! ok) CloseResFile (resFileId); } } } #endif return ok; } void close() { #if JUCE_PPC if (fragId != 0) { if (moduleMain != 0) disposeMachOFromCFM ((void*) moduleMain); CloseConnection (&fragId); HUnlock (resHandle); if (resFileId != 0) CloseResFile (resFileId); } else #endif if (bundleRef != 0) { CFBundleCloseBundleResourceMap (bundleRef, resFileId); if (CFGetRetainCount (bundleRef) == 1) CFBundleUnloadExecutable (bundleRef); if (CFGetRetainCount (bundleRef) > 0) CFRelease (bundleRef); } } void closeEffect (AEffect* eff) { #if JUCE_PPC if (fragId != 0) { Array thingsToDelete; thingsToDelete.add ((void*) eff->dispatcher); thingsToDelete.add ((void*) eff->process); thingsToDelete.add ((void*) eff->setParameter); thingsToDelete.add ((void*) eff->getParameter); thingsToDelete.add ((void*) eff->processReplacing); eff->dispatcher (eff, effClose, 0, 0, 0, 0); for (int i = thingsToDelete.size(); --i >= 0;) disposeMachOFromCFM (thingsToDelete[i]); } else #endif { eff->dispatcher (eff, effClose, 0, 0, 0, 0); } } #if JUCE_PPC static void* newMachOFromCFM (void* cfmfp) { if (cfmfp == 0) return nullptr; UInt32* const mfp = new UInt32[6]; mfp[0] = 0x3d800000 | ((UInt32) cfmfp >> 16); mfp[1] = 0x618c0000 | ((UInt32) cfmfp & 0xffff); mfp[2] = 0x800c0000; mfp[3] = 0x804c0004; mfp[4] = 0x7c0903a6; mfp[5] = 0x4e800420; MakeDataExecutable (mfp, sizeof (UInt32) * 6); return mfp; } static void disposeMachOFromCFM (void* ptr) { delete[] static_cast (ptr); } void coerceAEffectFunctionCalls (AEffect* eff) { if (fragId != 0) { eff->dispatcher = (AEffectDispatcherProc) newMachOFromCFM ((void*) eff->dispatcher); eff->process = (AEffectProcessProc) newMachOFromCFM ((void*) eff->process); eff->setParameter = (AEffectSetParameterProc) newMachOFromCFM ((void*) eff->setParameter); eff->getParameter = (AEffectGetParameterProc) newMachOFromCFM ((void*) eff->getParameter); eff->processReplacing = (AEffectProcessProc) newMachOFromCFM ((void*) eff->processReplacing); } } #endif #endif private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleHandle); }; /** An instance of a plugin, created by a VSTPluginFormat. */ class VSTPluginInstance : public AudioPluginInstance, private Timer, private AsyncUpdater { public: ~VSTPluginInstance(); // AudioPluginInstance methods: void fillInPluginDescription (PluginDescription& desc) const { desc.name = name; { char buffer [512] = { 0 }; dispatch (effGetEffectName, 0, 0, buffer, 0); desc.descriptiveName = String (buffer).trim(); if (desc.descriptiveName.isEmpty()) desc.descriptiveName = name; } desc.fileOrIdentifier = module->file.getFullPathName(); desc.uid = getUID(); desc.lastFileModTime = module->file.getLastModificationTime(); desc.pluginFormatName = "VST"; desc.category = getCategory(); { char buffer [kVstMaxVendorStrLen + 8] = { 0 }; dispatch (effGetVendorString, 0, 0, buffer, 0); desc.manufacturerName = buffer; } desc.version = getVersion(); desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); desc.isInstrument = (effect != nullptr && (effect->flags & effFlagsIsSynth) != 0); } void* getPlatformSpecificData() { return effect; } const String getName() const { return name; } int getUID() const; bool acceptsMidi() const { return wantsMidiMessages; } bool producesMidi() const { return dispatch (effCanDo, 0, 0, (void*) "sendVstMidiEvent", 0) > 0; } // AudioProcessor methods: void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void releaseResources(); void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); bool hasEditor() const { return effect != nullptr && (effect->flags & effFlagsHasEditor) != 0; } AudioProcessorEditor* createEditor(); const String getInputChannelName (int index) const; bool isInputChannelStereoPair (int index) const; const String getOutputChannelName (int index) const; bool isOutputChannelStereoPair (int index) const; int getNumParameters() { return effect != nullptr ? effect->numParams : 0; } float getParameter (int index); void setParameter (int index, float newValue); const String getParameterName (int index); const String getParameterText (int index); bool isParameterAutomatable (int index) const; int getNumPrograms() { return effect != nullptr ? effect->numPrograms : 0; } int getCurrentProgram() { return dispatch (effGetProgram, 0, 0, 0, 0); } void setCurrentProgram (int index); const String getProgramName (int index); void changeProgramName (int index, const String& newName); void getStateInformation (MemoryBlock& destData); void getCurrentProgramStateInformation (MemoryBlock& destData); void setStateInformation (const void* data, int sizeInBytes); void setCurrentProgramStateInformation (const void* data, int sizeInBytes); void timerCallback(); void handleAsyncUpdate(); VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt); private: friend class VSTPluginWindow; friend class VSTPluginFormat; AEffect* effect; String name; CriticalSection lock; bool wantsMidiMessages, initialised, isPowerOn; mutable StringArray programNames; AudioSampleBuffer tempBuffer; CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; VstTimeInfo vstHostTime; ReferenceCountedObjectPtr module; int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const; bool restoreProgramSettings (const fxProgram* const prog); const String getCurrentProgramName(); void setParamsInProgramBlock (fxProgram* const prog); void updateStoredProgramNames(); void initialise(); void handleMidiFromPlugin (const VstEvents* const events); void createTempParameterStore (MemoryBlock& dest); void restoreFromTempParameterStore (const MemoryBlock& mb); const String getParameterLabel (int index) const; bool usesChunks() const noexcept { return effect != nullptr && (effect->flags & effFlagsProgramChunks) != 0; } void getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const; void setChunkData (const char* data, int size, bool isPreset); bool loadFromFXBFile (const void* data, int numBytes); bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB); int getVersionNumber() const noexcept { return effect != nullptr ? effect->version : 0; } const String getVersion() const; const String getCategory() const; void setPower (const bool on); VSTPluginInstance (const ReferenceCountedObjectPtr & module); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance); }; VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr & module_) : effect (nullptr), name (module_->pluginName), wantsMidiMessages (false), initialised (false), isPowerOn (false), tempBuffer (1, 1), module (module_) { try { const IdleCallRecursionPreventer icrp; _fpreset(); log ("Creating VST instance: " + name); #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #if JUCE_PPC if (module->fragId != 0) { static void* audioMasterCoerced = nullptr; if (audioMasterCoerced == nullptr) audioMasterCoerced = NewCFMFromMachO ((void*) &audioMaster); effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); } else #endif #endif { effect = module->moduleMain (&audioMaster); } if (effect != nullptr && effect->magic == kEffectMagic) { #if JUCE_PPC module->coerceAEffectFunctionCalls (effect); #endif jassert (effect->resvd2 == 0); jassert (effect->object != 0); _fpreset(); // some dodgy plugs fuck around with this } else { effect = nullptr; } } catch (...) {} } VSTPluginInstance::~VSTPluginInstance() { const ScopedLock sl (lock); if (effect != nullptr && effect->magic == kEffectMagic) { try { #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #endif // Must delete any editors before deleting the plugin instance! jassert (getActiveEditor() == 0); _fpreset(); // some dodgy plugs fuck around with this module->closeEffect (effect); } catch (...) {} } module = nullptr; effect = nullptr; } void VSTPluginInstance::initialise() { if (initialised || effect == 0) return; log ("Initialising VST: " + module->pluginName); initialised = true; dispatch (effIdentify, 0, 0, 0, 0); if (getSampleRate() > 0) dispatch (effSetSampleRate, 0, 0, 0, (float) getSampleRate()); if (getBlockSize() > 0) dispatch (effSetBlockSize, 0, jmax (32, getBlockSize()), 0, 0); dispatch (effOpen, 0, 0, 0, 0); setPlayConfigDetails (effect->numInputs, effect->numOutputs, getSampleRate(), getBlockSize()); if (getNumPrograms() > 1) setCurrentProgram (0); else dispatch (effSetProgram, 0, 0, 0, 0); int i; for (i = effect->numInputs; --i >= 0;) dispatch (effConnectInput, i, 1, 0, 0); for (i = effect->numOutputs; --i >= 0;) dispatch (effConnectOutput, i, 1, 0, 0); updateStoredProgramNames(); wantsMidiMessages = dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) > 0; setLatencySamples (effect->initialDelay); } void VSTPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) { setPlayConfigDetails (effect->numInputs, effect->numOutputs, sampleRate_, samplesPerBlockExpected); setLatencySamples (effect->initialDelay); vstHostTime.tempo = 120.0; vstHostTime.timeSigNumerator = 4; vstHostTime.timeSigDenominator = 4; vstHostTime.sampleRate = sampleRate_; vstHostTime.samplePos = 0; vstHostTime.flags = kVstNanosValid; /*| kVstTransportPlaying | kVstTempoValid | kVstTimeSigValid*/; initialise(); if (initialised) { wantsMidiMessages = wantsMidiMessages || (dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) > 0); if (wantsMidiMessages) midiEventsToSend.ensureSize (256); else midiEventsToSend.freeEvents(); incomingMidi.clear(); dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate_); dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); if (! isPowerOn) setPower (true); // dodgy hack to force some plugins to initialise the sample rate.. if ((! hasEditor()) && getNumParameters() > 0) { const float old = getParameter (0); setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); setParameter (0, old); } dispatch (effStartProcess, 0, 0, 0, 0); } } void VSTPluginInstance::releaseResources() { if (initialised) { dispatch (effStopProcess, 0, 0, 0, 0); setPower (false); } tempBuffer.setSize (1, 1); incomingMidi.clear(); midiEventsToSend.freeEvents(); } void VSTPluginInstance::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); if (initialised) { AudioPlayHead* playHead = getPlayHead(); if (playHead != nullptr) { AudioPlayHead::CurrentPositionInfo position; playHead->getCurrentPosition (position); vstHostTime.tempo = position.bpm; vstHostTime.timeSigNumerator = position.timeSigNumerator; vstHostTime.timeSigDenominator = position.timeSigDenominator; vstHostTime.ppqPos = position.ppqPosition; vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; if (position.isPlaying) vstHostTime.flags |= kVstTransportPlaying; else vstHostTime.flags &= ~kVstTransportPlaying; } vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); if (wantsMidiMessages) { midiEventsToSend.clear(); midiEventsToSend.ensureSize (1); MidiBuffer::Iterator iter (midiMessages); const uint8* midiData; int numBytesOfMidiData, samplePosition; while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) { midiEventsToSend.addEvent (midiData, numBytesOfMidiData, jlimit (0, numSamples - 1, samplePosition)); } try { effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); } catch (...) {} } _clearfp(); if ((effect->flags & effFlagsCanReplacing) != 0) { try { effect->processReplacing (effect, buffer.getArrayOfChannels(), buffer.getArrayOfChannels(), numSamples); } catch (...) {} } else { tempBuffer.setSize (effect->numOutputs, numSamples); tempBuffer.clear(); try { effect->process (effect, buffer.getArrayOfChannels(), tempBuffer.getArrayOfChannels(), numSamples); } catch (...) {} for (int i = effect->numOutputs; --i >= 0;) buffer.copyFrom (i, 0, tempBuffer.getSampleData (i), numSamples); } } else { // Not initialised, so just bypass.. for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) buffer.clear (i, 0, buffer.getNumSamples()); } { // copy any incoming midi.. const ScopedLock sl (midiInLock); midiMessages.swapWith (incomingMidi); incomingMidi.clear(); } } void VSTPluginInstance::handleMidiFromPlugin (const VstEvents* const events) { if (events != nullptr) { const ScopedLock sl (midiInLock); VSTMidiEventList::addEventsToMidiBuffer (events, incomingMidi); } } static Array activeVSTWindows; class VSTPluginWindow : public AudioProcessorEditor, #if ! JUCE_MAC public ComponentMovementWatcher, #endif public Timer { public: VSTPluginWindow (VSTPluginInstance& plugin_) : AudioProcessorEditor (&plugin_), #if ! JUCE_MAC ComponentMovementWatcher (this), #endif plugin (plugin_), isOpen (false), recursiveResize (false), pluginWantsKeys (false), pluginRefusesToResize (false), alreadyInside (false) { #if JUCE_WINDOWS sizeCheckCount = 0; pluginHWND = 0; #elif JUCE_LINUX pluginWindow = None; pluginProc = None; #else addAndMakeVisible (innerWrapper = new InnerWrapperComponent (this)); #endif activeVSTWindows.add (this); setSize (1, 1); setOpaque (true); setVisible (true); } ~VSTPluginWindow() { #if JUCE_MAC innerWrapper = nullptr; #else closePluginWindow(); #endif activeVSTWindows.removeValue (this); plugin.editorBeingDeleted (this); } #if ! JUCE_MAC void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { if (recursiveResize) return; Component* const topComp = getTopLevelComponent(); if (topComp->getPeer() != nullptr) { const Point pos (topComp->getLocalPoint (this, Point())); recursiveResize = true; #if JUCE_WINDOWS if (pluginHWND != 0) MoveWindow (pluginHWND, pos.getX(), pos.getY(), getWidth(), getHeight(), TRUE); #elif JUCE_LINUX if (pluginWindow != 0) { XResizeWindow (display, pluginWindow, getWidth(), getHeight()); XMoveWindow (display, pluginWindow, pos.getX(), pos.getY()); XMapRaised (display, pluginWindow); } #endif recursiveResize = false; } } void componentVisibilityChanged() { if (isShowing()) openPluginWindow(); else closePluginWindow(); componentMovedOrResized (true, true); } void componentPeerChanged() { closePluginWindow(); openPluginWindow(); } #endif bool keyStateChanged (bool) { return pluginWantsKeys; } bool keyPressed (const KeyPress&) { return pluginWantsKeys; } #if JUCE_MAC void paint (Graphics& g) { g.fillAll (Colours::black); } #else void paint (Graphics& g) { if (isOpen) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) { const Point pos (getScreenPosition() - peer->getScreenPosition()); peer->addMaskedRegion (pos.getX(), pos.getY(), getWidth(), getHeight()); #if JUCE_LINUX if (pluginWindow != 0) { const Rectangle clip (g.getClipBounds()); XEvent ev = { 0 }; ev.xexpose.type = Expose; ev.xexpose.display = display; ev.xexpose.window = pluginWindow; ev.xexpose.x = clip.getX(); ev.xexpose.y = clip.getY(); ev.xexpose.width = clip.getWidth(); ev.xexpose.height = clip.getHeight(); sendEventToChild (&ev); } #endif } } else { g.fillAll (Colours::black); } } #endif void timerCallback() { #if JUCE_WINDOWS if (--sizeCheckCount <= 0) { sizeCheckCount = 10; checkPluginWindowSize(); } #endif try { static bool reentrant = false; if (! reentrant) { reentrant = true; plugin.dispatch (effEditIdle, 0, 0, 0, 0); reentrant = false; } } catch (...) {} } void mouseDown (const MouseEvent& e) { #if JUCE_LINUX if (pluginWindow == 0) return; toFront (true); XEvent ev = { 0 }; ev.xbutton.display = display; ev.xbutton.type = ButtonPress; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXButtonModifiers (e, ev); sendEventToChild (&ev); #elif JUCE_WINDOWS (void) e; toFront (true); #endif } void broughtToFront() { activeVSTWindows.removeValue (this); activeVSTWindows.add (this); #if JUCE_MAC dispatch (effEditTop, 0, 0, 0, 0); #endif } private: VSTPluginInstance& plugin; bool isOpen, recursiveResize; bool pluginWantsKeys, pluginRefusesToResize, alreadyInside; #if JUCE_WINDOWS HWND pluginHWND; void* originalWndProc; int sizeCheckCount; #elif JUCE_LINUX Window pluginWindow; EventProcPtr pluginProc; #endif #if JUCE_MAC void openPluginWindow (WindowRef parentWindow) { if (isOpen || parentWindow == 0) return; isOpen = true; ERect* rect = nullptr; dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effEditOpen, 0, 0, parentWindow, 0); // do this before and after like in the steinberg example dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); // double-check it's not too tiny int w = 250, h = 150; if (rect != nullptr) { w = rect->right - rect->left; h = rect->bottom - rect->top; if (w == 0 || h == 0) { w = 250; h = 150; } } w = jmax (w, 32); h = jmax (h, 32); setSize (w, h); startTimer (18 + JUCE_NAMESPACE::Random::getSystemRandom().nextInt (5)); repaint(); } #else void openPluginWindow() { if (isOpen || getWindowHandle() == 0) return; log ("Opening VST UI: " + plugin.name); isOpen = true; ERect* rect = nullptr; dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effEditOpen, 0, 0, getWindowHandle(), 0); // do this before and after like in the steinberg example dispatch (effEditGetRect, 0, 0, &rect, 0); dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); #if JUCE_WINDOWS originalWndProc = 0; pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); if (pluginHWND == 0) { isOpen = false; setSize (300, 150); return; } #pragma warning (push) #pragma warning (disable: 4244) originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWLP_WNDPROC); if (! pluginWantsKeys) SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) vstHookWndProc); #pragma warning (pop) int w, h; RECT r; GetWindowRect (pluginHWND, &r); w = r.right - r.left; h = r.bottom - r.top; if (rect != nullptr) { const int rw = rect->right - rect->left; const int rh = rect->bottom - rect->top; if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) { // very dodgy logic to decide which size is right. if (abs (rw - w) > 350 || abs (rh - h) > 350) { SetWindowPos (pluginHWND, 0, 0, 0, rw, rh, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); GetWindowRect (pluginHWND, &r); w = r.right - r.left; h = r.bottom - r.top; pluginRefusesToResize = (w != rw) || (h != rh); w = rw; h = rh; } } } #elif JUCE_LINUX pluginWindow = getChildWindow ((Window) getWindowHandle()); if (pluginWindow != 0) pluginProc = (EventProcPtr) getPropertyFromXWindow (pluginWindow, XInternAtom (display, "_XEventProc", False)); int w = 250, h = 150; if (rect != nullptr) { w = rect->right - rect->left; h = rect->bottom - rect->top; if (w == 0 || h == 0) { w = 250; h = 150; } } if (pluginWindow != 0) XMapRaised (display, pluginWindow); #endif // double-check it's not too tiny w = jmax (w, 32); h = jmax (h, 32); setSize (w, h); #if JUCE_WINDOWS checkPluginWindowSize(); #endif startTimer (18 + JUCE_NAMESPACE::Random::getSystemRandom().nextInt (5)); repaint(); } #endif #if ! JUCE_MAC void closePluginWindow() { if (isOpen) { log ("Closing VST UI: " + plugin.getName()); isOpen = false; dispatch (effEditClose, 0, 0, 0, 0); #if JUCE_WINDOWS #pragma warning (push) #pragma warning (disable: 4244) if (pluginHWND != 0 && IsWindow (pluginHWND)) SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); #pragma warning (pop) stopTimer(); if (pluginHWND != 0 && IsWindow (pluginHWND)) DestroyWindow (pluginHWND); pluginHWND = 0; #elif JUCE_LINUX stopTimer(); pluginWindow = 0; pluginProc = 0; #endif } } #endif int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) { return plugin.dispatch (opcode, index, value, ptr, opt); } #if JUCE_WINDOWS void checkPluginWindowSize() { RECT r; GetWindowRect (pluginHWND, &r); const int w = r.right - r.left; const int h = r.bottom - r.top; if (isShowing() && w > 0 && h > 0 && (w != getWidth() || h != getHeight()) && ! pluginRefusesToResize) { setSize (w, h); sizeCheckCount = 0; } } // hooks to get keyboard events from VST windows.. static LRESULT CALLBACK vstHookWndProc (HWND hW, UINT message, WPARAM wParam, LPARAM lParam) { for (int i = activeVSTWindows.size(); --i >= 0;) { const VSTPluginWindow* const w = activeVSTWindows.getUnchecked (i); if (w->pluginHWND == hW) { if (message == WM_CHAR || message == WM_KEYDOWN || message == WM_SYSKEYDOWN || message == WM_KEYUP || message == WM_SYSKEYUP || message == WM_APPCOMMAND) { SendMessage ((HWND) w->getTopLevelComponent()->getWindowHandle(), message, wParam, lParam); } return CallWindowProc ((WNDPROC) (w->originalWndProc), (HWND) w->pluginHWND, message, wParam, lParam); } } return DefWindowProc (hW, message, wParam, lParam); } #endif #if JUCE_LINUX // overload mouse/keyboard events to forward them to the plugin's inner window.. void sendEventToChild (XEvent* event) { if (pluginProc != 0) { // if the plugin publishes an event procedure, pass the event directly.. pluginProc (event); } else if (pluginWindow != 0) { // if the plugin has a window, then send the event to the window so that // its message thread will pick it up.. XSendEvent (display, pluginWindow, False, 0L, event); XFlush (display); } } void mouseEnter (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xcrossing.display = display; ev.xcrossing.type = EnterNotify; ev.xcrossing.window = pluginWindow; ev.xcrossing.root = RootWindow (display, DefaultScreen (display)); ev.xcrossing.time = CurrentTime; ev.xcrossing.x = e.x; ev.xcrossing.y = e.y; ev.xcrossing.x_root = e.getScreenX(); ev.xcrossing.y_root = e.getScreenY(); ev.xcrossing.mode = NotifyNormal; // NotifyGrab, NotifyUngrab ev.xcrossing.detail = NotifyAncestor; // NotifyVirtual, NotifyInferior, NotifyNonlinear,NotifyNonlinearVirtual translateJuceToXCrossingModifiers (e, ev); sendEventToChild (&ev); } } void mouseExit (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xcrossing.display = display; ev.xcrossing.type = LeaveNotify; ev.xcrossing.window = pluginWindow; ev.xcrossing.root = RootWindow (display, DefaultScreen (display)); ev.xcrossing.time = CurrentTime; ev.xcrossing.x = e.x; ev.xcrossing.y = e.y; ev.xcrossing.x_root = e.getScreenX(); ev.xcrossing.y_root = e.getScreenY(); ev.xcrossing.mode = NotifyNormal; // NotifyGrab, NotifyUngrab ev.xcrossing.detail = NotifyAncestor; // NotifyVirtual, NotifyInferior, NotifyNonlinear,NotifyNonlinearVirtual ev.xcrossing.focus = hasKeyboardFocus (true); // TODO - yes ? translateJuceToXCrossingModifiers (e, ev); sendEventToChild (&ev); } } void mouseMove (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xmotion.display = display; ev.xmotion.type = MotionNotify; ev.xmotion.window = pluginWindow; ev.xmotion.root = RootWindow (display, DefaultScreen (display)); ev.xmotion.time = CurrentTime; ev.xmotion.is_hint = NotifyNormal; ev.xmotion.x = e.x; ev.xmotion.y = e.y; ev.xmotion.x_root = e.getScreenX(); ev.xmotion.y_root = e.getScreenY(); sendEventToChild (&ev); } } void mouseDrag (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xmotion.display = display; ev.xmotion.type = MotionNotify; ev.xmotion.window = pluginWindow; ev.xmotion.root = RootWindow (display, DefaultScreen (display)); ev.xmotion.time = CurrentTime; ev.xmotion.x = e.x ; ev.xmotion.y = e.y; ev.xmotion.x_root = e.getScreenX(); ev.xmotion.y_root = e.getScreenY(); ev.xmotion.is_hint = NotifyNormal; translateJuceToXMotionModifiers (e, ev); sendEventToChild (&ev); } } void mouseUp (const MouseEvent& e) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xbutton.display = display; ev.xbutton.type = ButtonRelease; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXButtonModifiers (e, ev); sendEventToChild (&ev); } } void mouseWheelMove (const MouseEvent& e, float incrementX, float incrementY) { if (pluginWindow != 0) { XEvent ev = { 0 }; ev.xbutton.display = display; ev.xbutton.type = ButtonPress; ev.xbutton.window = pluginWindow; ev.xbutton.root = RootWindow (display, DefaultScreen (display)); ev.xbutton.time = CurrentTime; ev.xbutton.x = e.x; ev.xbutton.y = e.y; ev.xbutton.x_root = e.getScreenX(); ev.xbutton.y_root = e.getScreenY(); translateJuceToXMouseWheelModifiers (e, incrementY, ev); sendEventToChild (&ev); // TODO - put a usleep here ? ev.xbutton.type = ButtonRelease; sendEventToChild (&ev); } } #endif #if JUCE_MAC #if ! JUCE_SUPPORT_CARBON #error "To build VSTs, you need to enable the JUCE_SUPPORT_CARBON flag in your config!" #endif class InnerWrapperComponent : public CarbonViewWrapperComponent { public: InnerWrapperComponent (VSTPluginWindow* const owner_) : owner (owner_), alreadyInside (false) { } ~InnerWrapperComponent() { deleteWindow(); } HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) { owner->openPluginWindow (windowRef); return 0; } void removeView (HIViewRef) { owner->dispatch (effEditClose, 0, 0, 0, 0); owner->dispatch (effEditSleep, 0, 0, 0, 0); } bool getEmbeddedViewSize (int& w, int& h) { ERect* rect = nullptr; owner->dispatch (effEditGetRect, 0, 0, &rect, 0); w = rect->right - rect->left; h = rect->bottom - rect->top; return true; } void mouseDown (int x, int y) { if (! alreadyInside) { alreadyInside = true; getTopLevelComponent()->toFront (true); owner->dispatch (effEditMouse, x, y, 0, 0); alreadyInside = false; } else { PostEvent (::mouseDown, 0); } } void paint() { ComponentPeer* const peer = getPeer(); if (peer != nullptr) { const Point pos (getScreenPosition() - peer->getScreenPosition()); ERect r; r.left = pos.getX(); r.right = r.left + getWidth(); r.top = pos.getY(); r.bottom = r.top + getHeight(); owner->dispatch (effEditDraw, 0, 0, &r, 0); } } private: VSTPluginWindow* const owner; bool alreadyInside; }; friend class InnerWrapperComponent; ScopedPointer innerWrapper; void resized() { innerWrapper->setSize (getWidth(), getHeight()); } #endif private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow); }; AudioProcessorEditor* VSTPluginInstance::createEditor() { if (hasEditor()) return new VSTPluginWindow (*this); return nullptr; } void VSTPluginInstance::handleAsyncUpdate() { // indicates that something about the plugin has changed.. updateHostDisplay(); } bool VSTPluginInstance::restoreProgramSettings (const fxProgram* const prog) { if (vst_swap (prog->chunkMagic) == 'CcnK' && vst_swap (prog->fxMagic) == 'FxCk') { changeProgramName (getCurrentProgram(), prog->prgName); for (int i = 0; i < vst_swap (prog->numParams); ++i) setParameter (i, vst_swapFloat (prog->params[i])); return true; } return false; } bool VSTPluginInstance::loadFromFXBFile (const void* const data, const int dataSize) { if (dataSize < 28) return false; const fxSet* const set = (const fxSet*) data; if ((vst_swap (set->chunkMagic) != 'CcnK' && vst_swap (set->chunkMagic) != 'KncC') || vst_swap (set->version) > fxbVersionNum) return false; if (vst_swap (set->fxMagic) == 'FxBk') { // bank of programs if (vst_swap (set->numPrograms) >= 0) { const int oldProg = getCurrentProgram(); const int numParams = vst_swap (((const fxProgram*) (set->programs))->numParams); const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); for (int i = 0; i < vst_swap (set->numPrograms); ++i) { if (i != oldProg) { const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); if (((const char*) prog) - ((const char*) set) >= dataSize) return false; if (vst_swap (set->numPrograms) > 0) setCurrentProgram (i); if (! restoreProgramSettings (prog)) return false; } } if (vst_swap (set->numPrograms) > 0) setCurrentProgram (oldProg); const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); if (((const char*) prog) - ((const char*) set) >= dataSize) return false; if (! restoreProgramSettings (prog)) return false; } } else if (vst_swap (set->fxMagic) == 'FxCk') { // single program const fxProgram* const prog = (const fxProgram*) data; if (vst_swap (prog->chunkMagic) != 'CcnK') return false; changeProgramName (getCurrentProgram(), prog->prgName); for (int i = 0; i < vst_swap (prog->numParams); ++i) setParameter (i, vst_swapFloat (prog->params[i])); } else if (vst_swap (set->fxMagic) == 'FBCh' || vst_swap (set->fxMagic) == 'hCBF') { // non-preset chunk const fxChunkSet* const cset = (const fxChunkSet*) data; if (vst_swap (cset->chunkSize) + sizeof (fxChunkSet) - 8 > (unsigned int) dataSize) return false; setChunkData (cset->chunk, vst_swap (cset->chunkSize), false); } else if (vst_swap (set->fxMagic) == 'FPCh' || vst_swap (set->fxMagic) == 'hCPF') { // preset chunk const fxProgramSet* const cset = (const fxProgramSet*) data; if (vst_swap (cset->chunkSize) + sizeof (fxProgramSet) - 8 > (unsigned int) dataSize) return false; setChunkData (cset->chunk, vst_swap (cset->chunkSize), true); changeProgramName (getCurrentProgram(), cset->name); } else { return false; } return true; } void VSTPluginInstance::setParamsInProgramBlock (fxProgram* const prog) { const int numParams = getNumParameters(); prog->chunkMagic = vst_swap ('CcnK'); prog->byteSize = 0; prog->fxMagic = vst_swap ('FxCk'); prog->version = vst_swap (fxbVersionNum); prog->fxID = vst_swap (getUID()); prog->fxVersion = vst_swap (getVersionNumber()); prog->numParams = vst_swap (numParams); getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); for (int i = 0; i < numParams; ++i) prog->params[i] = vst_swapFloat (getParameter (i)); } bool VSTPluginInstance::saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB) { const int numPrograms = getNumPrograms(); const int numParams = getNumParameters(); if (usesChunks()) { if (isFXB) { MemoryBlock chunk; getChunkData (chunk, false, maxSizeMB); const size_t totalLen = sizeof (fxChunkSet) + chunk.getSize() - 8; dest.setSize (totalLen, true); fxChunkSet* const set = (fxChunkSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FBCh'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); set->chunkSize = vst_swap ((long) chunk.getSize()); chunk.copyTo (set->chunk, 0, chunk.getSize()); } else { MemoryBlock chunk; getChunkData (chunk, true, maxSizeMB); const size_t totalLen = sizeof (fxProgramSet) + chunk.getSize() - 8; dest.setSize (totalLen, true); fxProgramSet* const set = (fxProgramSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FPCh'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); set->chunkSize = vst_swap ((long) chunk.getSize()); getCurrentProgramName().copyToUTF8 (set->name, sizeof (set->name) - 1); chunk.copyTo (set->chunk, 0, chunk.getSize()); } } else { if (isFXB) { const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); const int len = (sizeof (fxSet) - sizeof (fxProgram)) + progLen * jmax (1, numPrograms); dest.setSize (len, true); fxSet* const set = (fxSet*) dest.getData(); set->chunkMagic = vst_swap ('CcnK'); set->byteSize = 0; set->fxMagic = vst_swap ('FxBk'); set->version = vst_swap (fxbVersionNum); set->fxID = vst_swap (getUID()); set->fxVersion = vst_swap (getVersionNumber()); set->numPrograms = vst_swap (numPrograms); const int oldProgram = getCurrentProgram(); MemoryBlock oldSettings; createTempParameterStore (oldSettings); setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); for (int i = 0; i < numPrograms; ++i) { if (i != oldProgram) { setCurrentProgram (i); setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); } } setCurrentProgram (oldProgram); restoreFromTempParameterStore (oldSettings); } else { const int totalLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); dest.setSize (totalLen, true); setParamsInProgramBlock ((fxProgram*) dest.getData()); } } return true; } void VSTPluginInstance::getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const { if (usesChunks()) { void* data = nullptr; const int bytes = dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); if (data != nullptr && bytes <= maxSizeMB * 1024 * 1024) { mb.setSize (bytes); mb.copyFrom (data, 0, bytes); } } } void VSTPluginInstance::setChunkData (const char* data, int size, bool isPreset) { if (size > 0 && usesChunks()) { dispatch (effSetChunk, isPreset ? 1 : 0, size, (void*) data, 0.0f); if (! isPreset) updateStoredProgramNames(); } } void VSTPluginInstance::timerCallback() { if (dispatch (effIdle, 0, 0, 0, 0) == 0) stopTimer(); } int VSTPluginInstance::dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const { int result = 0; if (effect != nullptr) { const ScopedLock sl (lock); const IdleCallRecursionPreventer icrp; try { #if JUCE_MAC if (module->resFileId != 0) UseResFile (module->resFileId); #endif result = effect->dispatcher (effect, opcode, index, value, ptr, opt); #if JUCE_MAC module->resFileId = CurResFile(); #endif } catch (...) {} } return result; } namespace { static const int defaultVSTSampleRateValue = 16384; static const int defaultVSTBlockSizeValue = 512; // handles non plugin-specific callbacks.. VstIntPtr handleGeneralCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) { (void) index; (void) value; (void) opt; switch (opcode) { case audioMasterCanDo: { static const char* canDos[] = { "supplyIdle", "sendVstEvents", "sendVstMidiEvent", "sendVstTimeInfo", "receiveVstEvents", "receiveVstMidiEvent", "supportShell", "shellCategory" }; for (int i = 0; i < numElementsInArray (canDos); ++i) if (strcmp (canDos[i], (const char*) ptr) == 0) return 1; return 0; } case audioMasterVersion: return 0x2400; case audioMasterCurrentId: return shellUIDToCreate; case audioMasterGetNumAutomatableParameters: return 0; case audioMasterGetAutomationState: return 1; case audioMasterGetVendorVersion: return 0x0101; case audioMasterGetVendorString: case audioMasterGetProductString: { String hostName ("Juce VST Host"); if (JUCEApplication::getInstance() != nullptr) hostName = JUCEApplication::getInstance()->getApplicationName(); hostName.copyToUTF8 ((char*) ptr, jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); break; } case audioMasterGetSampleRate: return (VstIntPtr) defaultVSTSampleRateValue; case audioMasterGetBlockSize: return (VstIntPtr) defaultVSTBlockSizeValue; case audioMasterSetOutputSampleRate: return 0; default: DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); break; } return 0; } } // handles callbacks for a specific plugin VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) { switch (opcode) { case audioMasterAutomate: sendParamChangeMessageToListeners (index, opt); break; case audioMasterProcessEvents: handleMidiFromPlugin ((const VstEvents*) ptr); break; case audioMasterGetTime: #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4311) #endif return (VstIntPtr) &vstHostTime; #if JUCE_MSVC #pragma warning (pop) #endif break; case audioMasterIdle: if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) { const IdleCallRecursionPreventer icrp; #if JUCE_MAC if (getActiveEditor() != nullptr) dispatch (effEditIdle, 0, 0, 0, 0); #endif juce_callAnyTimersSynchronously(); handleUpdateNowIfNeeded(); for (int i = ComponentPeer::getNumPeers(); --i >= 0;) ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); } break; case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; case audioMasterTempoAt: // returns (10000 * bpm) break; case audioMasterNeedIdle: startTimer (50); break; case audioMasterSizeWindow: if (getActiveEditor() != nullptr) getActiveEditor()->setSize (index, value); return 1; case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); case audioMasterWantMidi: wantsMidiMessages = true; break; case audioMasterGetDirectory: #if JUCE_MAC return (VstIntPtr) (void*) &module->parentDirFSSpec; #else return (VstIntPtr) (pointer_sized_uint) module->fullParentDirectoryPathName.toUTF8().getAddress(); #endif case audioMasterGetAutomationState: // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write break; // none of these are handled (yet).. case audioMasterBeginEdit: case audioMasterEndEdit: case audioMasterSetTime: case audioMasterPinConnected: case audioMasterGetParameterQuantization: case audioMasterIOChanged: case audioMasterGetInputLatency: case audioMasterGetOutputLatency: case audioMasterGetPreviousPlug: case audioMasterGetNextPlug: case audioMasterWillReplaceOrAccumulate: case audioMasterGetCurrentProcessLevel: case audioMasterOfflineStart: case audioMasterOfflineRead: case audioMasterOfflineWrite: case audioMasterOfflineGetCurrentPass: case audioMasterOfflineGetCurrentMetaPass: case audioMasterVendorSpecific: case audioMasterSetIcon: case audioMasterGetLanguage: case audioMasterOpenWindow: case audioMasterCloseWindow: break; default: return handleGeneralCallback (opcode, index, value, ptr, opt); } return 0; } // entry point for all callbacks from the plugin static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) { try { if (effect != nullptr && effect->resvd2 != 0) { return ((VSTPluginInstance*)(effect->resvd2)) ->handleCallback (opcode, index, value, ptr, opt); } return handleGeneralCallback (opcode, index, value, ptr, opt); } catch (...) { return 0; } } const String VSTPluginInstance::getVersion() const { unsigned int v = dispatch (effGetVendorVersion, 0, 0, 0, 0); String s; if (v == 0 || v == -1) v = getVersionNumber(); if (v != 0) { int versionBits[4]; int n = 0; while (v != 0) { versionBits [n++] = (v & 0xff); v >>= 8; } s << 'V'; while (n > 0) { s << versionBits [--n]; if (n > 0) s << '.'; } } return s; } int VSTPluginInstance::getUID() const { int uid = effect != nullptr ? effect->uniqueID : 0; if (uid == 0) uid = module->file.hashCode(); return uid; } const String VSTPluginInstance::getCategory() const { const char* result = nullptr; switch (dispatch (effGetPlugCategory, 0, 0, 0, 0)) { case kPlugCategEffect: result = "Effect"; break; case kPlugCategSynth: result = "Synth"; break; case kPlugCategAnalysis: result = "Anaylsis"; break; case kPlugCategMastering: result = "Mastering"; break; case kPlugCategSpacializer: result = "Spacial"; break; case kPlugCategRoomFx: result = "Reverb"; break; case kPlugSurroundFx: result = "Surround"; break; case kPlugCategRestoration: result = "Restoration"; break; case kPlugCategGenerator: result = "Tone generation"; break; default: break; } return result; } float VSTPluginInstance::getParameter (int index) { if (effect != nullptr && isPositiveAndBelow (index, (int) effect->numParams)) { try { const ScopedLock sl (lock); return effect->getParameter (effect, index); } catch (...) { } } return 0.0f; } void VSTPluginInstance::setParameter (int index, float newValue) { if (effect != nullptr && isPositiveAndBelow (index, (int) effect->numParams)) { try { const ScopedLock sl (lock); if (effect->getParameter (effect, index) != newValue) effect->setParameter (effect, index, newValue); } catch (...) { } } } const String VSTPluginInstance::getParameterName (int index) { if (effect != nullptr) { jassert (index >= 0 && index < effect->numParams); char nm [256] = { 0 }; dispatch (effGetParamName, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterLabel (int index) const { if (effect != nullptr) { jassert (index >= 0 && index < effect->numParams); char nm [256] = { 0 }; dispatch (effGetParamLabel, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterText (int index) { if (effect != nullptr) { jassert (index >= 0 && index < effect->numParams); char nm [256] = { 0 }; dispatch (effGetParamDisplay, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } bool VSTPluginInstance::isParameterAutomatable (int index) const { if (effect != nullptr) { jassert (index >= 0 && index < effect->numParams); return dispatch (effCanBeAutomated, index, 0, 0, 0) != 0; } return false; } void VSTPluginInstance::createTempParameterStore (MemoryBlock& dest) { dest.setSize (64 + 4 * getNumParameters()); dest.fillWith (0); getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); float* const p = (float*) (((char*) dest.getData()) + 64); for (int i = 0; i < getNumParameters(); ++i) p[i] = getParameter(i); } void VSTPluginInstance::restoreFromTempParameterStore (const MemoryBlock& m) { changeProgramName (getCurrentProgram(), (const char*) m.getData()); float* p = (float*) (((char*) m.getData()) + 64); for (int i = 0; i < getNumParameters(); ++i) setParameter (i, p[i]); } void VSTPluginInstance::setCurrentProgram (int newIndex) { if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) dispatch (effSetProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); } const String VSTPluginInstance::getProgramName (int index) { if (index == getCurrentProgram()) { return getCurrentProgramName(); } else if (effect != nullptr) { char nm [256] = { 0 }; if (dispatch (effGetProgramNameIndexed, jlimit (0, getNumPrograms(), index), -1, nm, 0) != 0) { return String (nm).trim(); } } return programNames [index]; } void VSTPluginInstance::changeProgramName (int index, const String& newName) { if (index == getCurrentProgram()) { if (getNumPrograms() > 0 && newName != getCurrentProgramName()) dispatch (effSetProgramName, 0, 0, (void*) newName.substring (0, 24).toUTF8().getAddress(), 0.0f); } else { jassertfalse; // xxx not implemented! } } void VSTPluginInstance::updateStoredProgramNames() { if (effect != nullptr && getNumPrograms() > 0) { char nm [256] = { 0 }; // only do this if the plugin can't use indexed names.. if (dispatch (effGetProgramNameIndexed, 0, -1, nm, 0) == 0) { const int oldProgram = getCurrentProgram(); MemoryBlock oldSettings; createTempParameterStore (oldSettings); for (int i = 0; i < getNumPrograms(); ++i) { setCurrentProgram (i); getCurrentProgramName(); // (this updates the list) } setCurrentProgram (oldProgram); restoreFromTempParameterStore (oldSettings); } } } const String VSTPluginInstance::getCurrentProgramName() { if (effect != nullptr) { char nm [256] = { 0 }; dispatch (effGetProgramName, 0, 0, nm, 0); const int index = getCurrentProgram(); if (programNames[index].isEmpty()) { while (programNames.size() < index) programNames.add (String::empty); programNames.set (index, String (nm).trim()); } return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getInputChannelName (int index) const { if (index >= 0 && index < getNumInputChannels()) { VstPinProperties pinProps; if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) return String (pinProps.label, sizeof (pinProps.label)); } return String::empty; } bool VSTPluginInstance::isInputChannelStereoPair (int index) const { if (index < 0 || index >= getNumInputChannels()) return false; VstPinProperties pinProps; if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) return (pinProps.flags & kVstPinIsStereo) != 0; return true; } const String VSTPluginInstance::getOutputChannelName (int index) const { if (index >= 0 && index < getNumOutputChannels()) { VstPinProperties pinProps; if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) return String (pinProps.label, sizeof (pinProps.label)); } return String::empty; } bool VSTPluginInstance::isOutputChannelStereoPair (int index) const { if (index < 0 || index >= getNumOutputChannels()) return false; VstPinProperties pinProps; if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) return (pinProps.flags & kVstPinIsStereo) != 0; return true; } void VSTPluginInstance::setPower (const bool on) { dispatch (effMainsChanged, 0, on ? 1 : 0, 0, 0); isPowerOn = on; } const int defaultMaxSizeMB = 64; void VSTPluginInstance::getStateInformation (MemoryBlock& destData) { saveToFXBFile (destData, true, defaultMaxSizeMB); } void VSTPluginInstance::getCurrentProgramStateInformation (MemoryBlock& destData) { saveToFXBFile (destData, false, defaultMaxSizeMB); } void VSTPluginInstance::setStateInformation (const void* data, int sizeInBytes) { loadFromFXBFile (data, sizeInBytes); } void VSTPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { loadFromFXBFile (data, sizeInBytes); } VSTPluginFormat::VSTPluginFormat() { } VSTPluginFormat::~VSTPluginFormat() { } void VSTPluginFormat::findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier) { if (! fileMightContainThisPluginType (fileOrIdentifier)) return; PluginDescription desc; desc.fileOrIdentifier = fileOrIdentifier; desc.uid = 0; ScopedPointer instance (dynamic_cast (createInstanceFromDescription (desc))); if (instance == 0) return; try { #if JUCE_MAC if (instance->module->resFileId != 0) UseResFile (instance->module->resFileId); #endif instance->fillInPluginDescription (desc); VstPlugCategory category = (VstPlugCategory) instance->dispatch (effGetPlugCategory, 0, 0, 0, 0); if (category != kPlugCategShell) { // Normal plugin... results.add (new PluginDescription (desc)); instance->dispatch (effOpen, 0, 0, 0, 0); } else { // It's a shell plugin, so iterate all the subtypes... for (;;) { char shellEffectName [64] = { 0 }; const int uid = instance->dispatch (effShellGetNextPlugin, 0, 0, shellEffectName, 0); if (uid == 0) { break; } else { desc.uid = uid; desc.name = shellEffectName; desc.descriptiveName = shellEffectName; bool alreadyThere = false; for (int i = results.size(); --i >= 0;) { PluginDescription* const d = results.getUnchecked(i); if (d->isDuplicateOf (desc)) { alreadyThere = true; break; } } if (! alreadyThere) results.add (new PluginDescription (desc)); } } } } catch (...) { // crashed while loading... } } AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const PluginDescription& desc) { ScopedPointer result; if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { File file (desc.fileOrIdentifier); const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); file.getParentDirectory().setAsCurrentWorkingDirectory(); const ReferenceCountedObjectPtr module (ModuleHandle::findOrCreateModule (file)); if (module != nullptr) { shellUIDToCreate = desc.uid; result = new VSTPluginInstance (module); if (result->effect != nullptr) { result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) (VSTPluginInstance*) result; result->initialise(); } else { result = nullptr; } } previousWorkingDirectory.setAsCurrentWorkingDirectory(); } return result.release(); } bool VSTPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) { const File f (fileOrIdentifier); #if JUCE_MAC if (f.isDirectory() && f.hasFileExtension (".vst")) return true; #if JUCE_PPC FSRef fileRef; if (PlatformUtilities::makeFSRefFromPath (&fileRef, f.getFullPathName())) { const short resFileId = FSOpenResFile (&fileRef, fsRdPerm); if (resFileId != -1) { const int numEffects = Count1Resources ('aEff'); CloseResFile (resFileId); if (numEffects > 0) return true; } } #endif return false; #elif JUCE_WINDOWS return f.existsAsFile() && f.hasFileExtension (".dll"); #elif JUCE_LINUX return f.existsAsFile() && f.hasFileExtension (".so"); #endif } const String VSTPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } bool VSTPluginFormat::doesPluginStillExist (const PluginDescription& desc) { return File (desc.fileOrIdentifier).exists(); } const StringArray VSTPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) { StringArray results; for (int j = 0; j < directoriesToSearch.getNumPaths(); ++j) recursiveFileSearch (results, directoriesToSearch [j], recursive); return results; } void VSTPluginFormat::recursiveFileSearch (StringArray& results, const File& dir, const bool recursive) { // avoid allowing the dir iterator to be recursive, because we want to avoid letting it delve inside // .component or .vst directories. DirectoryIterator iter (dir, false, "*", File::findFilesAndDirectories); while (iter.next()) { const File f (iter.getFile()); bool isPlugin = false; if (fileMightContainThisPluginType (f.getFullPathName())) { isPlugin = true; results.add (f.getFullPathName()); } if (recursive && (! isPlugin) && f.isDirectory()) recursiveFileSearch (results, f, true); } } const FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() { #if JUCE_MAC return FileSearchPath ("~/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST"); #elif JUCE_WINDOWS const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); return FileSearchPath (programFiles + "\\Steinberg\\VstPlugins"); #elif JUCE_LINUX return FileSearchPath ("/usr/lib/vst"); #endif } END_JUCE_NAMESPACE #endif #undef log #endif /*** End of inlined file: juce_VSTPluginFormat.cpp ***/ /*** End of inlined file: juce_VSTPluginFormat.mm ***/ /*** Start of inlined file: juce_AudioProcessor.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessor::AudioProcessor() : playHead (nullptr), sampleRate (0), blockSize (0), numInputChannels (0), numOutputChannels (0), latencySamples (0), suspended (false), nonRealtime (false) { } AudioProcessor::~AudioProcessor() { // ooh, nasty - the editor should have been deleted before the filter // that it refers to is deleted.. jassert (activeEditor == nullptr); #if JUCE_DEBUG // This will fail if you've called beginParameterChangeGesture() for one // or more parameters without having made a corresponding call to endParameterChangeGesture... jassert (changingParams.countNumberOfSetBits() == 0); #endif } void AudioProcessor::setPlayHead (AudioPlayHead* const newPlayHead) noexcept { playHead = newPlayHead; } void AudioProcessor::addListener (AudioProcessorListener* const newListener) { const ScopedLock sl (listenerLock); listeners.addIfNotAlreadyThere (newListener); } void AudioProcessor::removeListener (AudioProcessorListener* const listenerToRemove) { const ScopedLock sl (listenerLock); listeners.removeValue (listenerToRemove); } void AudioProcessor::setPlayConfigDetails (const int numIns, const int numOuts, const double sampleRate_, const int blockSize_) noexcept { numInputChannels = numIns; numOutputChannels = numOuts; sampleRate = sampleRate_; blockSize = blockSize_; } void AudioProcessor::setNonRealtime (const bool nonRealtime_) noexcept { nonRealtime = nonRealtime_; } void AudioProcessor::setLatencySamples (const int newLatency) { if (latencySamples != newLatency) { latencySamples = newLatency; updateHostDisplay(); } } void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, const float newValue) { setParameter (parameterIndex, newValue); sendParamChangeMessageToListeners (parameterIndex, newValue); } void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex, const float newValue) { jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != nullptr) l->audioProcessorParameterChanged (this, parameterIndex, newValue); } } void AudioProcessor::beginParameterChangeGesture (int parameterIndex) { jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called beginParameterChangeGesture twice in succession without a matching // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. jassert (! changingParams [parameterIndex]); changingParams.setBit (parameterIndex); #endif for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != nullptr) l->audioProcessorParameterChangeGestureBegin (this, parameterIndex); } } void AudioProcessor::endParameterChangeGesture (int parameterIndex) { jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called endParameterChangeGesture without having previously called // endParameterChangeGesture. That might be fine in most hosts, but better to keep the // calls matched correctly. jassert (changingParams [parameterIndex]); changingParams.clearBit (parameterIndex); #endif for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != nullptr) l->audioProcessorParameterChangeGestureEnd (this, parameterIndex); } } void AudioProcessor::updateHostDisplay() { for (int i = listeners.size(); --i >= 0;) { AudioProcessorListener* l; { const ScopedLock sl (listenerLock); l = listeners [i]; } if (l != nullptr) l->audioProcessorChanged (this); } } bool AudioProcessor::isParameterAutomatable (int /*parameterIndex*/) const { return true; } bool AudioProcessor::isMetaParameter (int /*parameterIndex*/) const { return false; } void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) { const ScopedLock sl (callbackLock); suspended = shouldBeSuspended; } void AudioProcessor::reset() { } void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept { const ScopedLock sl (callbackLock); if (activeEditor == editor) activeEditor = nullptr; } AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() { if (activeEditor != nullptr) return activeEditor; AudioProcessorEditor* const ed = createEditor(); // You must make your hasEditor() method return a consistent result! jassert (hasEditor() == (ed != nullptr)); if (ed != nullptr) { // you must give your editor comp a size before returning it.. jassert (ed->getWidth() > 0 && ed->getHeight() > 0); const ScopedLock sl (callbackLock); activeEditor = ed; } return ed; } void AudioProcessor::getCurrentProgramStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) { getStateInformation (destData); } void AudioProcessor::setCurrentProgramStateInformation (const void* data, int sizeInBytes) { setStateInformation (data, sizeInBytes); } // magic number to identify memory blocks that we've stored as XML const uint32 magicXmlNumber = 0x21324356; void AudioProcessor::copyXmlToBinary (const XmlElement& xml, JUCE_NAMESPACE::MemoryBlock& destData) { const String xmlString (xml.createDocument (String::empty, true, false)); const int stringLength = xmlString.getNumBytesAsUTF8(); destData.setSize (stringLength + 10); char* const d = static_cast (destData.getData()); *(uint32*) d = ByteOrder::swapIfBigEndian ((const uint32) magicXmlNumber); *(uint32*) (d + 4) = ByteOrder::swapIfBigEndian ((const uint32) stringLength); xmlString.copyToUTF8 (d + 8, stringLength + 1); } XmlElement* AudioProcessor::getXmlFromBinary (const void* data, const int sizeInBytes) { if (sizeInBytes > 8 && ByteOrder::littleEndianInt (data) == magicXmlNumber) { const int stringLength = (int) ByteOrder::littleEndianInt (addBytesToPointer (data, 4)); if (stringLength > 0) return XmlDocument::parse (String::fromUTF8 (static_cast (data) + 8, jmin ((sizeInBytes - 8), stringLength))); } return nullptr; } void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept { return timeInSeconds == other.timeInSeconds && ppqPosition == other.ppqPosition && editOriginTime == other.editOriginTime && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart && frameRate == other.frameRate && isPlaying == other.isPlaying && isRecording == other.isRecording && bpm == other.bpm && timeSigNumerator == other.timeSigNumerator && timeSigDenominator == other.timeSigDenominator; } bool AudioPlayHead::CurrentPositionInfo::operator!= (const CurrentPositionInfo& other) const noexcept { return ! operator== (other); } void AudioPlayHead::CurrentPositionInfo::resetToDefault() { zerostruct (*this); timeSigNumerator = 4; timeSigDenominator = 4; bpm = 120; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessor.cpp ***/ /*** Start of inlined file: juce_AudioProcessorEditor.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* const owner_) : owner (owner_) { // the filter must be valid.. jassert (owner != nullptr); } AudioProcessorEditor::~AudioProcessorEditor() { // if this fails, then the wrapper hasn't called editorBeingDeleted() on the // filter for some reason.. jassert (owner->getActiveEditor() != this); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorEditor.cpp ***/ /*** Start of inlined file: juce_AudioProcessorGraph.cpp ***/ BEGIN_JUCE_NAMESPACE const int AudioProcessorGraph::midiChannelIndex = 0x1000; namespace GraphRenderingOps { class AudioGraphRenderingOp { public: AudioGraphRenderingOp() {} virtual ~AudioGraphRenderingOp() {} virtual void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray & sharedMidiBuffers, const int numSamples) = 0; JUCE_LEAK_DETECTOR (AudioGraphRenderingOp); }; class ClearChannelOp : public AudioGraphRenderingOp { public: ClearChannelOp (const int channelNum_) : channelNum (channelNum_) {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.clear (channelNum, 0, numSamples); } private: const int channelNum; JUCE_DECLARE_NON_COPYABLE (ClearChannelOp); }; class CopyChannelOp : public AudioGraphRenderingOp { public: CopyChannelOp (const int srcChannelNum_, const int dstChannelNum_) : srcChannelNum (srcChannelNum_), dstChannelNum (dstChannelNum_) {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } private: const int srcChannelNum, dstChannelNum; JUCE_DECLARE_NON_COPYABLE (CopyChannelOp); }; class AddChannelOp : public AudioGraphRenderingOp { public: AddChannelOp (const int srcChannelNum_, const int dstChannelNum_) : srcChannelNum (srcChannelNum_), dstChannelNum (dstChannelNum_) {} void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } private: const int srcChannelNum, dstChannelNum; JUCE_DECLARE_NON_COPYABLE (AddChannelOp); }; class ClearMidiBufferOp : public AudioGraphRenderingOp { public: ClearMidiBufferOp (const int bufferNum_) : bufferNum (bufferNum_) {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int) { sharedMidiBuffers.getUnchecked (bufferNum)->clear(); } private: const int bufferNum; JUCE_DECLARE_NON_COPYABLE (ClearMidiBufferOp); }; class CopyMidiBufferOp : public AudioGraphRenderingOp { public: CopyMidiBufferOp (const int srcBufferNum_, const int dstBufferNum_) : srcBufferNum (srcBufferNum_), dstBufferNum (dstBufferNum_) {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int) { *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); } private: const int srcBufferNum, dstBufferNum; JUCE_DECLARE_NON_COPYABLE (CopyMidiBufferOp); }; class AddMidiBufferOp : public AudioGraphRenderingOp { public: AddMidiBufferOp (const int srcBufferNum_, const int dstBufferNum_) : srcBufferNum (srcBufferNum_), dstBufferNum (dstBufferNum_) {} void perform (AudioSampleBuffer&, const OwnedArray & sharedMidiBuffers, const int numSamples) { sharedMidiBuffers.getUnchecked (dstBufferNum) ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); } private: const int srcBufferNum, dstBufferNum; JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp); }; class DelayChannelOp : public AudioGraphRenderingOp { public: DelayChannelOp (const int channel_, const int numSamplesDelay_) : channel (channel_), bufferSize (numSamplesDelay_ + 1), readIndex (0), writeIndex (numSamplesDelay_) { buffer.calloc (bufferSize); } void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) { float* data = sharedBufferChans.getSampleData (channel, 0); for (int i = numSamples; --i >= 0;) { buffer [writeIndex] = *data; *data++ = buffer [readIndex]; if (++readIndex >= bufferSize) readIndex = 0; if (++writeIndex >= bufferSize) writeIndex = 0; } } private: HeapBlock buffer; const int channel, bufferSize; int readIndex, writeIndex; JUCE_DECLARE_NON_COPYABLE (DelayChannelOp); }; class ProcessBufferOp : public AudioGraphRenderingOp { public: ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& node_, const Array & audioChannelsToUse_, const int totalChans_, const int midiBufferToUse_) : node (node_), processor (node_->getProcessor()), audioChannelsToUse (audioChannelsToUse_), totalChans (jmax (1, totalChans_)), midiBufferToUse (midiBufferToUse_) { channels.calloc (totalChans); while (audioChannelsToUse.size() < totalChans) audioChannelsToUse.add (0); } void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray & sharedMidiBuffers, const int numSamples) { for (int i = totalChans; --i >= 0;) channels[i] = sharedBufferChans.getSampleData (audioChannelsToUse.getUnchecked (i), 0); AudioSampleBuffer buffer (channels, totalChans, numSamples); processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); } const AudioProcessorGraph::Node::Ptr node; AudioProcessor* const processor; private: Array audioChannelsToUse; HeapBlock channels; int totalChans; int midiBufferToUse; JUCE_DECLARE_NON_COPYABLE (ProcessBufferOp); }; /** Used to calculate the correct sequence of rendering ops needed, based on the best re-use of shared buffers at each stage. */ class RenderingOpSequenceCalculator { public: RenderingOpSequenceCalculator (AudioProcessorGraph& graph_, const Array& orderedNodes_, Array& renderingOps) : graph (graph_), orderedNodes (orderedNodes_), totalLatency (0) { nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros channels.add (0); midiNodeIds.add ((uint32) zeroNodeID); for (int i = 0; i < orderedNodes.size(); ++i) { createRenderingOpsForNode ((AudioProcessorGraph::Node*) orderedNodes.getUnchecked(i), renderingOps, i); markAnyUnusedBuffersAsFree (i); } graph.setLatencySamples (totalLatency); } int getNumBuffersNeeded() const { return nodeIds.size(); } int getNumMidiBuffersNeeded() const { return midiNodeIds.size(); } private: AudioProcessorGraph& graph; const Array& orderedNodes; Array channels; Array nodeIds, midiNodeIds; enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe }; static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } Array nodeDelayIDs; Array nodeDelays; int totalLatency; int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } void setNodeDelay (const uint32 nodeID, const int latency) { const int index = nodeDelayIDs.indexOf (nodeID); if (index >= 0) { nodeDelays.set (index, latency); } else { nodeDelayIDs.add (nodeID); nodeDelays.add (latency); } } int getInputLatencyForNode (const uint32 nodeID) const { int maxLatency = 0; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == nodeID) maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); } return maxLatency; } void createRenderingOpsForNode (AudioProcessorGraph::Node* const node, Array& renderingOps, const int ourRenderingIndex) { const int numIns = node->getProcessor()->getNumInputChannels(); const int numOuts = node->getProcessor()->getNumOutputChannels(); const int totalChans = jmax (numIns, numOuts); Array audioChannelsToUse; int midiBufferToUse = -1; int maxLatency = getInputLatencyForNode (node->nodeId); for (int inputChan = 0; inputChan < numIns; ++inputChan) { // get a list of all the inputs to this node Array sourceNodes, sourceOutputChans; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node->nodeId && c->destChannelIndex == inputChan) { sourceNodes.add (c->sourceNodeId); sourceOutputChans.add (c->sourceChannelIndex); } } int bufIndex = -1; if (sourceNodes.size() == 0) { // unconnected input channel if (inputChan >= numOuts) { bufIndex = getReadOnlyEmptyBuffer(); jassert (bufIndex >= 0); } else { bufIndex = getFreeBuffer (false); renderingOps.add (new ClearChannelOp (bufIndex)); } } else if (sourceNodes.size() == 1) { // channel with a straightforward single input.. const int srcNode = sourceNodes.getUnchecked(0); const int srcChan = sourceOutputChans.getUnchecked(0); bufIndex = getBufferContaining (srcNode, srcChan); if (bufIndex < 0) { // if not found, this is probably a feedback loop bufIndex = getReadOnlyEmptyBuffer(); jassert (bufIndex >= 0); } if (inputChan < numOuts && isBufferNeededLater (ourRenderingIndex, inputChan, srcNode, srcChan)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (false); renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer)); bufIndex = newFreeBuffer; } const int nodeDelay = getNodeDelay (srcNode); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } else { // channel with a mix of several inputs.. // try to find a re-usable channel from our inputs.. int reusableInputIndex = -1; for (int i = 0; i < sourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i)); if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, inputChan, sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i))) { // we've found one of our input chans that can be re-used.. reusableInputIndex = i; bufIndex = sourceBufIndex; const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); break; } } if (reusableInputIndex < 0) { // can't re-use any of our input chans, so get a new one and copy everything into it.. bufIndex = getFreeBuffer (false); jassert (bufIndex != 0); const int srcIndex = getBufferContaining (sourceNodes.getUnchecked (0), sourceOutputChans.getUnchecked (0)); if (srcIndex < 0) { // if not found, this is probably a feedback loop renderingOps.add (new ClearChannelOp (bufIndex)); } else { renderingOps.add (new CopyChannelOp (srcIndex, bufIndex)); } reusableInputIndex = 0; const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } for (int j = 0; j < sourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) { const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); if (nodeDelay < maxLatency) { if (! isBufferNeededLater (ourRenderingIndex, inputChan, sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j))) { renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); } else // buffer is reused elsewhere, can't be delayed { const int bufferToDelay = getFreeBuffer (false); renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); renderingOps.add (new AddChannelOp (bufferToDelay, bufIndex)); } } else { renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); } } } } } jassert (bufIndex >= 0); audioChannelsToUse.add (bufIndex); if (inputChan < numOuts) markBufferAsContaining (bufIndex, node->nodeId, inputChan); } for (int outputChan = numIns; outputChan < numOuts; ++outputChan) { const int bufIndex = getFreeBuffer (false); jassert (bufIndex != 0); audioChannelsToUse.add (bufIndex); markBufferAsContaining (bufIndex, node->nodeId, outputChan); } // Now the same thing for midi.. Array midiSourceNodes; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node->nodeId && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex) midiSourceNodes.add (c->sourceNodeId); } if (midiSourceNodes.size() == 0) { // No midi inputs.. midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi if (node->getProcessor()->acceptsMidi() || node->getProcessor()->producesMidi()) renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); } else if (midiSourceNodes.size() == 1) { // One midi input.. midiBufferToUse = getBufferContaining (midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex); if (midiBufferToUse >= 0) { if (isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (true); renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); midiBufferToUse = newFreeBuffer; } } else { // probably a feedback loop, so just use an empty one.. midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi } } else { // More than one midi input being mixed.. int reusableInputIndex = -1; for (int i = 0; i < midiSourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (midiSourceNodes.getUnchecked(i), AudioProcessorGraph::midiChannelIndex); if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, midiSourceNodes.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { // we've found one of our input buffers that can be re-used.. reusableInputIndex = i; midiBufferToUse = sourceBufIndex; break; } } if (reusableInputIndex < 0) { // can't re-use any of our input buffers, so get a new one and copy everything into it.. midiBufferToUse = getFreeBuffer (true); jassert (midiBufferToUse >= 0); const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(0), AudioProcessorGraph::midiChannelIndex); if (srcIndex >= 0) renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); else renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); reusableInputIndex = 0; } for (int j = 0; j < midiSourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(j), AudioProcessorGraph::midiChannelIndex); if (srcIndex >= 0) renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); } } } if (node->getProcessor()->producesMidi()) markBufferAsContaining (midiBufferToUse, node->nodeId, AudioProcessorGraph::midiChannelIndex); setNodeDelay (node->nodeId, maxLatency + node->getProcessor()->getLatencySamples()); if (numOuts == 0) totalLatency = maxLatency; renderingOps.add (new ProcessBufferOp (node, audioChannelsToUse, totalChans, midiBufferToUse)); } int getFreeBuffer (const bool forMidi) { if (forMidi) { for (int i = 1; i < midiNodeIds.size(); ++i) if (midiNodeIds.getUnchecked(i) == freeNodeID) return i; midiNodeIds.add ((uint32) freeNodeID); return midiNodeIds.size() - 1; } else { for (int i = 1; i < nodeIds.size(); ++i) if (nodeIds.getUnchecked(i) == freeNodeID) return i; nodeIds.add ((uint32) freeNodeID); channels.add (0); return nodeIds.size() - 1; } } int getReadOnlyEmptyBuffer() const noexcept { return 0; } int getBufferContaining (const uint32 nodeId, const int outputChannel) const noexcept { if (outputChannel == AudioProcessorGraph::midiChannelIndex) { for (int i = midiNodeIds.size(); --i >= 0;) if (midiNodeIds.getUnchecked(i) == nodeId) return i; } else { for (int i = nodeIds.size(); --i >= 0;) if (nodeIds.getUnchecked(i) == nodeId && channels.getUnchecked(i) == outputChannel) return i; } return -1; } void markAnyUnusedBuffersAsFree (const int stepIndex) { int i; for (i = 0; i < nodeIds.size(); ++i) { if (isNodeBusy (nodeIds.getUnchecked(i)) && ! isBufferNeededLater (stepIndex, -1, nodeIds.getUnchecked(i), channels.getUnchecked(i))) { nodeIds.set (i, (uint32) freeNodeID); } } for (i = 0; i < midiNodeIds.size(); ++i) { if (isNodeBusy (midiNodeIds.getUnchecked(i)) && ! isBufferNeededLater (stepIndex, -1, midiNodeIds.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { midiNodeIds.set (i, (uint32) freeNodeID); } } } bool isBufferNeededLater (int stepIndexToSearchFrom, int inputChannelOfIndexToIgnore, const uint32 nodeId, const int outputChanIndex) const { while (stepIndexToSearchFrom < orderedNodes.size()) { const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); if (outputChanIndex == AudioProcessorGraph::midiChannelIndex) { if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex && graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex, node->nodeId, AudioProcessorGraph::midiChannelIndex) != nullptr) return true; } else { for (int i = 0; i < node->getProcessor()->getNumInputChannels(); ++i) if (i != inputChannelOfIndexToIgnore && graph.getConnectionBetween (nodeId, outputChanIndex, node->nodeId, i) != nullptr) return true; } inputChannelOfIndexToIgnore = -1; ++stepIndexToSearchFrom; } return false; } void markBufferAsContaining (int bufferNum, uint32 nodeId, int outputIndex) { if (outputIndex == AudioProcessorGraph::midiChannelIndex) { jassert (bufferNum > 0 && bufferNum < midiNodeIds.size()); midiNodeIds.set (bufferNum, nodeId); } else { jassert (bufferNum >= 0 && bufferNum < nodeIds.size()); nodeIds.set (bufferNum, nodeId); channels.set (bufferNum, outputIndex); } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderingOpSequenceCalculator); }; // Holds a fast lookup table for checking which nodes are inputs to others. class ConnectionLookupTable { public: explicit ConnectionLookupTable (const OwnedArray& connections) { for (int i = 0; i < connections.size(); ++i) { const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i); int index; Entry* entry = findEntry (c->destNodeId, index); if (entry == nullptr) { entry = new Entry (c->destNodeId); entries.insert (index, entry); } entry->srcNodes.add (c->sourceNodeId); } } bool isAnInputTo (const uint32 possibleInputId, const uint32 possibleDestinationId) const noexcept { return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size()); } private: struct Entry { explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {} const uint32 destNodeId; SortedSet srcNodes; JUCE_DECLARE_NON_COPYABLE (Entry); }; OwnedArray entries; bool isAnInputToRecursive (const uint32 possibleInputId, const uint32 possibleDestinationId, int recursionCheck) const noexcept { int index; const Entry* const entry = findEntry (possibleDestinationId, index); if (entry != nullptr) { const SortedSet& srcNodes = entry->srcNodes; if (srcNodes.contains (possibleInputId)) return true; if (--recursionCheck >= 0) { for (int i = 0; i < srcNodes.size(); ++i) if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck)) return true; } } return false; } Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept { Entry* result = nullptr; int start = 0; int end = entries.size(); for (;;) { if (start >= end) { break; } else if (destNodeId == entries.getUnchecked (start)->destNodeId) { result = entries.getUnchecked (start); break; } else { const int halfway = (start + end) / 2; if (halfway == start) { if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) ++start; break; } else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) start = halfway; else end = halfway; } } insertIndex = start; return result; } JUCE_DECLARE_NON_COPYABLE (ConnectionLookupTable); }; struct ConnectionSorter { static int compareElements (const AudioProcessorGraph::Connection* const first, const AudioProcessorGraph::Connection* const second) noexcept { if (first->sourceNodeId < second->sourceNodeId) return -1; else if (first->sourceNodeId > second->sourceNodeId) return 1; else if (first->destNodeId < second->destNodeId) return -1; else if (first->destNodeId > second->destNodeId) return 1; else if (first->sourceChannelIndex < second->sourceChannelIndex) return -1; else if (first->sourceChannelIndex > second->sourceChannelIndex) return 1; else if (first->destChannelIndex < second->destChannelIndex) return -1; else if (first->destChannelIndex > second->destChannelIndex) return 1; return 0; } }; } AudioProcessorGraph::Connection::Connection (const uint32 sourceNodeId_, const int sourceChannelIndex_, const uint32 destNodeId_, const int destChannelIndex_) noexcept : sourceNodeId (sourceNodeId_), sourceChannelIndex (sourceChannelIndex_), destNodeId (destNodeId_), destChannelIndex (destChannelIndex_) { } AudioProcessorGraph::Node::Node (const uint32 nodeId_, AudioProcessor* const processor_) noexcept : nodeId (nodeId_), processor (processor_), isPrepared (false) { jassert (processor_ != nullptr); } void AudioProcessorGraph::Node::prepare (const double sampleRate, const int blockSize, AudioProcessorGraph* const graph) { if (! isPrepared) { isPrepared = true; AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (processor)); if (ioProc != nullptr) ioProc->setParentGraph (graph); processor->setPlayConfigDetails (processor->getNumInputChannels(), processor->getNumOutputChannels(), sampleRate, blockSize); processor->prepareToPlay (sampleRate, blockSize); } } void AudioProcessorGraph::Node::unprepare() { if (isPrepared) { isPrepared = false; processor->releaseResources(); } } AudioProcessorGraph::AudioProcessorGraph() : lastNodeId (0), renderingBuffers (1, 1), currentAudioOutputBuffer (1, 1) { } AudioProcessorGraph::~AudioProcessorGraph() { clearRenderingSequence(); clear(); } const String AudioProcessorGraph::getName() const { return "Audio Graph"; } void AudioProcessorGraph::clear() { nodes.clear(); connections.clear(); triggerAsyncUpdate(); } AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const { for (int i = nodes.size(); --i >= 0;) if (nodes.getUnchecked(i)->nodeId == nodeId) return nodes.getUnchecked(i); return nullptr; } AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) { if (newProcessor == nullptr) { jassertfalse; return nullptr; } if (nodeId == 0) { nodeId = ++lastNodeId; } else { // you can't add a node with an id that already exists in the graph.. jassert (getNodeForId (nodeId) == nullptr); removeNode (nodeId); if (nodeId > lastNodeId) lastNodeId = nodeId; } Node* const n = new Node (nodeId, newProcessor); nodes.add (n); triggerAsyncUpdate(); AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (n->processor)); if (ioProc != nullptr) ioProc->setParentGraph (this); return n; } bool AudioProcessorGraph::removeNode (const uint32 nodeId) { disconnectNode (nodeId); for (int i = nodes.size(); --i >= 0;) { if (nodes.getUnchecked(i)->nodeId == nodeId) { AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (nodes.getUnchecked(i)->processor)); if (ioProc != nullptr) ioProc->setParentGraph (nullptr); nodes.remove (i); triggerAsyncUpdate(); return true; } } return false; } const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) const { const Connection c (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex); GraphRenderingOps::ConnectionSorter sorter; return connections [connections.indexOfSorted (sorter, &c)]; } bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, const uint32 possibleDestNodeId) const { for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == possibleSourceNodeId && c->destNodeId == possibleDestNodeId) { return true; } } return false; } bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) const { if (sourceChannelIndex < 0 || destChannelIndex < 0 || sourceNodeId == destNodeId || (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex)) return false; const Node* const source = getNodeForId (sourceNodeId); if (source == nullptr || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getNumOutputChannels()) || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) return false; const Node* const dest = getNodeForId (destNodeId); if (dest == nullptr || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getNumInputChannels()) || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) return false; return getConnectionBetween (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex) == nullptr; } bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) { if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) return false; GraphRenderingOps::ConnectionSorter sorter; connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)); triggerAsyncUpdate(); return true; } void AudioProcessorGraph::removeConnection (const int index) { connections.remove (index); triggerAsyncUpdate(); } bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == sourceNodeId && c->destNodeId == destNodeId && c->sourceChannelIndex == sourceChannelIndex && c->destChannelIndex == destChannelIndex) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } bool AudioProcessorGraph::removeIllegalConnections() { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); const Node* const source = getNodeForId (c->sourceNodeId); const Node* const dest = getNodeForId (c->destNodeId); if (source == nullptr || dest == nullptr || (c->sourceChannelIndex != midiChannelIndex && ! isPositiveAndBelow (c->sourceChannelIndex, source->processor->getNumOutputChannels())) || (c->sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()) || (c->destChannelIndex != midiChannelIndex && ! isPositiveAndBelow (c->destChannelIndex, dest->processor->getNumInputChannels())) || (c->destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) { removeConnection (i); doneAnything = true; triggerAsyncUpdate(); } } return doneAnything; } void AudioProcessorGraph::clearRenderingSequence() { const ScopedLock sl (renderLock); for (int i = renderingOps.size(); --i >= 0;) { GraphRenderingOps::AudioGraphRenderingOp* const r = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); renderingOps.remove (i); delete r; } } bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, const uint32 possibleDestinationId, const int recursionCheck) const { if (recursionCheck > 0) { for (int i = connections.size(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); if (c->destNodeId == possibleDestinationId && (c->sourceNodeId == possibleInputId || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) return true; } } return false; } void AudioProcessorGraph::buildRenderingSequence() { Array newRenderingOps; int numRenderingBuffersNeeded = 2; int numMidiBuffersNeeded = 1; { MessageManagerLock mml; Array orderedNodes; { const GraphRenderingOps::ConnectionLookupTable table (connections); for (int i = 0; i < nodes.size(); ++i) { Node* const node = nodes.getUnchecked(i); node->prepare (getSampleRate(), getBlockSize(), this); int j = 0; for (; j < orderedNodes.size(); ++j) if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId)) break; orderedNodes.insert (j, node); } } GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); numRenderingBuffersNeeded = calculator.getNumBuffersNeeded(); numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); } Array oldRenderingOps (renderingOps); { // swap over to the new rendering sequence.. const ScopedLock sl (renderLock); renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); renderingBuffers.clear(); for (int i = midiBuffers.size(); --i >= 0;) midiBuffers.getUnchecked(i)->clear(); while (midiBuffers.size() < numMidiBuffersNeeded) midiBuffers.add (new MidiBuffer()); renderingOps = newRenderingOps; } for (int i = oldRenderingOps.size(); --i >= 0;) delete (GraphRenderingOps::AudioGraphRenderingOp*) oldRenderingOps.getUnchecked(i); } void AudioProcessorGraph::handleAsyncUpdate() { buildRenderingSequence(); } void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) { currentAudioInputBuffer = nullptr; currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); clearRenderingSequence(); buildRenderingSequence(); } void AudioProcessorGraph::releaseResources() { for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); renderingBuffers.setSize (1, 1); midiBuffers.clear(); currentAudioInputBuffer = nullptr; currentAudioOutputBuffer.setSize (1, 1); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); } void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { const int numSamples = buffer.getNumSamples(); const ScopedLock sl (renderLock); currentAudioInputBuffer = &buffer; currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); currentAudioOutputBuffer.clear(); currentMidiInputBuffer = &midiMessages; currentMidiOutputBuffer.clear(); int i; for (i = 0; i < renderingOps.size(); ++i) { GraphRenderingOps::AudioGraphRenderingOp* const op = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); op->perform (renderingBuffers, midiBuffers, numSamples); } for (i = 0; i < buffer.getNumChannels(); ++i) buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); midiMessages.clear(); midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); } const String AudioProcessorGraph::getInputChannelName (int channelIndex) const { return "Input " + String (channelIndex + 1); } const String AudioProcessorGraph::getOutputChannelName (int channelIndex) const { return "Output " + String (channelIndex + 1); } bool AudioProcessorGraph::isInputChannelStereoPair (int /*index*/) const { return true; } bool AudioProcessorGraph::isOutputChannelStereoPair (int /*index*/) const { return true; } bool AudioProcessorGraph::acceptsMidi() const { return true; } bool AudioProcessorGraph::producesMidi() const { return true; } void AudioProcessorGraph::getStateInformation (JUCE_NAMESPACE::MemoryBlock& /*destData*/) {} void AudioProcessorGraph::setStateInformation (const void* /*data*/, int /*sizeInBytes*/) {} AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType type_) : type (type_), graph (nullptr) { } AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() { } const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const { switch (type) { case audioOutputNode: return "Audio Output"; case audioInputNode: return "Audio Input"; case midiOutputNode: return "Midi Output"; case midiInputNode: return "Midi Input"; default: break; } return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription& d) const { d.name = getName(); d.uid = d.name.hashCode(); d.category = "I/O devices"; d.pluginFormatName = "Internal"; d.manufacturerName = "Raw Material Software"; d.version = "1.0"; d.isInstrument = false; d.numInputChannels = getNumInputChannels(); if (type == audioOutputNode && graph != nullptr) d.numInputChannels = graph->getNumInputChannels(); d.numOutputChannels = getNumOutputChannels(); if (type == audioInputNode && graph != nullptr) d.numOutputChannels = graph->getNumOutputChannels(); } void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) { jassert (graph != nullptr); } void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { jassert (graph != nullptr); switch (type) { case audioOutputNode: { for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) { graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); } break; } case audioInputNode: { for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) { buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); } break; } case midiOutputNode: graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); break; case midiInputNode: midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); break; default: break; } } bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const { return type == midiOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const { return type == midiInputNode; } const String AudioProcessorGraph::AudioGraphIOProcessor::getInputChannelName (int channelIndex) const { switch (type) { case audioOutputNode: return "Output " + String (channelIndex + 1); case midiOutputNode: return "Midi Output"; default: break; } return String::empty; } const String AudioProcessorGraph::AudioGraphIOProcessor::getOutputChannelName (int channelIndex) const { switch (type) { case audioInputNode: return "Input " + String (channelIndex + 1); case midiInputNode: return "Midi Input"; default: break; } return String::empty; } bool AudioProcessorGraph::AudioGraphIOProcessor::isInputChannelStereoPair (int /*index*/) const { return type == audioInputNode || type == audioOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutputChannelStereoPair (int index) const { return isInputChannelStereoPair (index); } bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const { return type == audioInputNode || type == midiInputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const { return type == audioOutputNode || type == midiOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const { return false; } AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; } int AudioProcessorGraph::AudioGraphIOProcessor::getNumParameters() { return 0; } const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterName (int) { return String::empty; } float AudioProcessorGraph::AudioGraphIOProcessor::getParameter (int) { return 0.0f; } const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterText (int) { return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::setParameter (int, float) { } int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { } const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return String::empty; } void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) {} void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (JUCE_NAMESPACE::MemoryBlock&) {} void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int) {} void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph) { graph = newGraph; if (graph != nullptr) { setPlayConfigDetails (type == audioOutputNode ? graph->getNumOutputChannels() : 0, type == audioInputNode ? graph->getNumInputChannels() : 0, getSampleRate(), getBlockSize()); updateHostDisplay(); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorGraph.cpp ***/ /*** Start of inlined file: juce_AudioProcessorPlayer.cpp ***/ BEGIN_JUCE_NAMESPACE AudioProcessorPlayer::AudioProcessorPlayer() : processor (nullptr), sampleRate (0), blockSize (0), isPrepared (false), numInputChans (0), numOutputChans (0), tempBuffer (1, 1) { } AudioProcessorPlayer::~AudioProcessorPlayer() { setProcessor (nullptr); } void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) { if (processor != processorToPlay) { if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0) { processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); processorToPlay->prepareToPlay (sampleRate, blockSize); } AudioProcessor* oldOne; { const ScopedLock sl (lock); oldOne = isPrepared ? processor : nullptr; processor = processorToPlay; isPrepared = true; } if (oldOne != nullptr) oldOne->releaseResources(); } } void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData, const int numInputChannels, float** const outputChannelData, const int numOutputChannels, const int numSamples) { // these should have been prepared by audioDeviceAboutToStart()... jassert (sampleRate > 0 && blockSize > 0); incomingMidi.clear(); messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples); int i, totalNumChans = 0; if (numInputChannels > numOutputChannels) { // if there aren't enough output channels for the number of // inputs, we need to create some temporary extra ones (can't // use the input data in case it gets written to) tempBuffer.setSize (numInputChannels - numOutputChannels, numSamples, false, false, true); for (i = 0; i < numOutputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } for (i = numOutputChannels; i < numInputChannels; ++i) { channels[totalNumChans] = tempBuffer.getSampleData (i - numOutputChannels, 0); memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } } else { for (i = 0; i < numInputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; memcpy (channels[totalNumChans], inputChannelData[i], sizeof (float) * numSamples); ++totalNumChans; } for (i = numInputChannels; i < numOutputChannels; ++i) { channels[totalNumChans] = outputChannelData[i]; zeromem (channels[totalNumChans], sizeof (float) * numSamples); ++totalNumChans; } } AudioSampleBuffer buffer (channels, totalNumChans, numSamples); const ScopedLock sl (lock); if (processor != nullptr) { const ScopedLock sl2 (processor->getCallbackLock()); if (processor->isSuspended()) { for (i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * numSamples); } else { processor->processBlock (buffer, incomingMidi); } } } void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* device) { const ScopedLock sl (lock); sampleRate = device->getCurrentSampleRate(); blockSize = device->getCurrentBufferSizeSamples(); numInputChans = device->getActiveInputChannels().countNumberOfSetBits(); numOutputChans = device->getActiveOutputChannels().countNumberOfSetBits(); messageCollector.reset (sampleRate); zeromem (channels, sizeof (channels)); if (processor != nullptr) { if (isPrepared) processor->releaseResources(); AudioProcessor* const oldProcessor = processor; setProcessor (nullptr); setProcessor (oldProcessor); } } void AudioProcessorPlayer::audioDeviceStopped() { const ScopedLock sl (lock); if (processor != nullptr && isPrepared) processor->releaseResources(); sampleRate = 0.0; blockSize = 0; isPrepared = false; tempBuffer.setSize (1, 1); } void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) { messageCollector.addMessageToQueue (message); } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioProcessorPlayer.cpp ***/ /*** Start of inlined file: juce_GenericAudioProcessorEditor.cpp ***/ BEGIN_JUCE_NAMESPACE class ProcessorParameterPropertyComp : public PropertyComponent, public AudioProcessorListener, public Timer { public: ProcessorParameterPropertyComp (const String& name, AudioProcessor& owner_, const int index_) : PropertyComponent (name), owner (owner_), index (index_), paramHasChanged (false), slider (owner_, index_) { startTimer (100); addAndMakeVisible (&slider); owner_.addListener (this); } ~ProcessorParameterPropertyComp() { owner.removeListener (this); } void refresh() { paramHasChanged = false; slider.setValue (owner.getParameter (index), false); } void audioProcessorChanged (AudioProcessor*) {} void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) { if (parameterIndex == index) paramHasChanged = true; } void timerCallback() { if (paramHasChanged) { refresh(); startTimer (1000 / 50); } else { startTimer (jmin (1000 / 4, getTimerInterval() + 10)); } } private: class ParamSlider : public Slider { public: ParamSlider (AudioProcessor& owner_, const int index_) : owner (owner_), index (index_) { setRange (0.0, 1.0, 0.0); setSliderStyle (Slider::LinearBar); setTextBoxIsEditable (false); setScrollWheelEnabled (false); } void valueChanged() { const float newVal = (float) getValue(); if (owner.getParameter (index) != newVal) owner.setParameter (index, newVal); } const String getTextFromValue (double /*value*/) { return owner.getParameterText (index); } private: AudioProcessor& owner; const int index; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParamSlider); }; AudioProcessor& owner; const int index; bool volatile paramHasChanged; ParamSlider slider; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp); }; GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const owner_) : AudioProcessorEditor (owner_) { jassert (owner_ != nullptr); setOpaque (true); addAndMakeVisible (&panel); Array params; const int numParams = owner_->getNumParameters(); int totalHeight = 0; for (int i = 0; i < numParams; ++i) { String name (owner_->getParameterName (i)); if (name.trim().isEmpty()) name = "Unnamed"; ProcessorParameterPropertyComp* const pc = new ProcessorParameterPropertyComp (name, *owner_, i); params.add (pc); totalHeight += pc->getPreferredHeight(); } panel.addProperties (params); setSize (400, jlimit (25, 400, totalHeight)); } GenericAudioProcessorEditor::~GenericAudioProcessorEditor() { } void GenericAudioProcessorEditor::paint (Graphics& g) { g.fillAll (Colours::white); } void GenericAudioProcessorEditor::resized() { panel.setBounds (getLocalBounds()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_GenericAudioProcessorEditor.cpp ***/ /*** Start of inlined file: juce_Sampler.cpp ***/ BEGIN_JUCE_NAMESPACE SamplerSound::SamplerSound (const String& name_, AudioFormatReader& source, const BigInteger& midiNotes_, const int midiNoteForNormalPitch, const double attackTimeSecs, const double releaseTimeSecs, const double maxSampleLengthSeconds) : name (name_), midiNotes (midiNotes_), midiRootNote (midiNoteForNormalPitch) { sourceSampleRate = source.sampleRate; if (sourceSampleRate <= 0 || source.lengthInSamples <= 0) { length = 0; attackSamples = 0; releaseSamples = 0; } else { length = jmin ((int) source.lengthInSamples, (int) (maxSampleLengthSeconds * sourceSampleRate)); data = new AudioSampleBuffer (jmin (2, (int) source.numChannels), length + 4); data->readFromAudioReader (&source, 0, length + 4, 0, true, true); attackSamples = roundToInt (attackTimeSecs * sourceSampleRate); releaseSamples = roundToInt (releaseTimeSecs * sourceSampleRate); } } SamplerSound::~SamplerSound() { } bool SamplerSound::appliesToNote (const int midiNoteNumber) { return midiNotes [midiNoteNumber]; } bool SamplerSound::appliesToChannel (const int /*midiChannel*/) { return true; } SamplerVoice::SamplerVoice() : pitchRatio (0.0), sourceSamplePosition (0.0), lgain (0.0f), rgain (0.0f), isInAttack (false), isInRelease (false) { } SamplerVoice::~SamplerVoice() { } bool SamplerVoice::canPlaySound (SynthesiserSound* sound) { return dynamic_cast (sound) != nullptr; } void SamplerVoice::startNote (const int midiNoteNumber, const float velocity, SynthesiserSound* s, const int /*currentPitchWheelPosition*/) { const SamplerSound* const sound = dynamic_cast (s); jassert (sound != nullptr); // this object can only play SamplerSounds! if (sound != nullptr) { const double targetFreq = MidiMessage::getMidiNoteInHertz (midiNoteNumber); const double naturalFreq = MidiMessage::getMidiNoteInHertz (sound->midiRootNote); pitchRatio = (targetFreq * sound->sourceSampleRate) / (naturalFreq * getSampleRate()); sourceSamplePosition = 0.0; lgain = velocity; rgain = velocity; isInAttack = (sound->attackSamples > 0); isInRelease = false; if (isInAttack) { attackReleaseLevel = 0.0f; attackDelta = (float) (pitchRatio / sound->attackSamples); } else { attackReleaseLevel = 1.0f; attackDelta = 0.0f; } if (sound->releaseSamples > 0) { releaseDelta = (float) (-pitchRatio / sound->releaseSamples); } else { releaseDelta = 0.0f; } } } void SamplerVoice::stopNote (const bool allowTailOff) { if (allowTailOff) { isInAttack = false; isInRelease = true; } else { clearCurrentNote(); } } void SamplerVoice::pitchWheelMoved (const int /*newValue*/) { } void SamplerVoice::controllerMoved (const int /*controllerNumber*/, const int /*newValue*/) { } void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) { const SamplerSound* const playingSound = static_cast (getCurrentlyPlayingSound().getObject()); if (playingSound != nullptr) { const float* const inL = playingSound->data->getSampleData (0, 0); const float* const inR = playingSound->data->getNumChannels() > 1 ? playingSound->data->getSampleData (1, 0) : nullptr; float* outL = outputBuffer.getSampleData (0, startSample); float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : nullptr; while (--numSamples >= 0) { const int pos = (int) sourceSamplePosition; const float alpha = (float) (sourceSamplePosition - pos); const float invAlpha = 1.0f - alpha; // just using a very simple linear interpolation here.. float l = (inL [pos] * invAlpha + inL [pos + 1] * alpha); float r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha) : l; l *= lgain; r *= rgain; if (isInAttack) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += attackDelta; if (attackReleaseLevel >= 1.0f) { attackReleaseLevel = 1.0f; isInAttack = false; } } else if (isInRelease) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += releaseDelta; if (attackReleaseLevel <= 0.0f) { stopNote (false); break; } } if (outR != nullptr) { *outL++ += l; *outR++ += r; } else { *outL++ += (l + r) * 0.5f; } sourceSamplePosition += pitchRatio; if (sourceSamplePosition > playingSound->length) { stopNote (false); break; } } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Sampler.cpp ***/ /*** Start of inlined file: juce_Synthesiser.cpp ***/ BEGIN_JUCE_NAMESPACE SynthesiserSound::SynthesiserSound() { } SynthesiserSound::~SynthesiserSound() { } SynthesiserVoice::SynthesiserVoice() : currentSampleRate (44100.0), currentlyPlayingNote (-1), noteOnTime (0), keyIsDown (false), sostenutoPedalDown (false) { } SynthesiserVoice::~SynthesiserVoice() { } bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const { return currentlyPlayingSound != nullptr && currentlyPlayingSound->appliesToChannel (midiChannel); } void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) { currentSampleRate = newRate; } void SynthesiserVoice::clearCurrentNote() { currentlyPlayingNote = -1; currentlyPlayingSound = nullptr; } Synthesiser::Synthesiser() : sampleRate (0), lastNoteOnCounter (0), shouldStealNotes (true) { for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) lastPitchWheelValues[i] = 0x2000; } Synthesiser::~Synthesiser() { } SynthesiserVoice* Synthesiser::getVoice (const int index) const { const ScopedLock sl (lock); return voices [index]; } void Synthesiser::clearVoices() { const ScopedLock sl (lock); voices.clear(); } void Synthesiser::addVoice (SynthesiserVoice* const newVoice) { const ScopedLock sl (lock); voices.add (newVoice); } void Synthesiser::removeVoice (const int index) { const ScopedLock sl (lock); voices.remove (index); } void Synthesiser::clearSounds() { const ScopedLock sl (lock); sounds.clear(); } void Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) { const ScopedLock sl (lock); sounds.add (newSound); } void Synthesiser::removeSound (const int index) { const ScopedLock sl (lock); sounds.remove (index); } void Synthesiser::setNoteStealingEnabled (const bool shouldStealNotes_) { shouldStealNotes = shouldStealNotes_; } void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) { if (sampleRate != newRate) { const ScopedLock sl (lock); allNotesOff (0, false); sampleRate = newRate; for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); } } void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBuffer& midiData, int startSample, int numSamples) { // must set the sample rate before using this! jassert (sampleRate != 0); const ScopedLock sl (lock); MidiBuffer::Iterator midiIterator (midiData); midiIterator.setNextSamplePosition (startSample); MidiMessage m (0xf4, 0.0); while (numSamples > 0) { int midiEventPos; const bool useEvent = midiIterator.getNextEvent (m, midiEventPos) && midiEventPos < startSample + numSamples; const int numThisTime = useEvent ? midiEventPos - startSample : numSamples; if (numThisTime > 0) { for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime); } if (useEvent) handleMidiEvent (m); startSample += numThisTime; numSamples -= numThisTime; } } void Synthesiser::handleMidiEvent (const MidiMessage& m) { if (m.isNoteOn()) { noteOn (m.getChannel(), m.getNoteNumber(), m.getFloatVelocity()); } else if (m.isNoteOff()) { noteOff (m.getChannel(), m.getNoteNumber(), true); } else if (m.isAllNotesOff() || m.isAllSoundOff()) { allNotesOff (m.getChannel(), true); } else if (m.isPitchWheel()) { const int channel = m.getChannel(); const int wheelPos = m.getPitchWheelValue(); lastPitchWheelValues [channel - 1] = wheelPos; handlePitchWheel (channel, wheelPos); } else if (m.isController()) { handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); } } void Synthesiser::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { const ScopedLock sl (lock); for (int i = sounds.size(); --i >= 0;) { SynthesiserSound* const sound = sounds.getUnchecked(i); if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) { // If hitting a note that's still ringing, stop it first (it could be // still playing because of the sustain or sostenuto pedal). for (int j = voices.size(); --j >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (j); if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) stopVoice (voice, true); } startVoice (findFreeVoice (sound, shouldStealNotes), sound, midiChannel, midiNoteNumber, velocity); } } } void Synthesiser::startVoice (SynthesiserVoice* const voice, SynthesiserSound* const sound, const int midiChannel, const int midiNoteNumber, const float velocity) { if (voice != nullptr && sound != nullptr) { if (voice->currentlyPlayingSound != nullptr) voice->stopNote (false); voice->startNote (midiNoteNumber, velocity, sound, lastPitchWheelValues [midiChannel - 1]); voice->currentlyPlayingNote = midiNoteNumber; voice->noteOnTime = ++lastNoteOnCounter; voice->currentlyPlayingSound = sound; voice->keyIsDown = true; voice->sostenutoPedalDown = false; } } void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) { jassert (voice != nullptr); voice->stopNote (allowTailOff); // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); } void Synthesiser::noteOff (const int midiChannel, const int midiNoteNumber, const bool allowTailOff) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->getCurrentlyPlayingNote() == midiNoteNumber) { SynthesiserSound* const sound = voice->getCurrentlyPlayingSound(); if (sound != nullptr && sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) { voice->keyIsDown = false; if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) stopVoice (voice, allowTailOff); } } } } void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) voice->stopNote (allowTailOff); } sustainPedalsDown.clear(); } void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) voice->pitchWheelMoved (wheelValue); } } void Synthesiser::handleController (const int midiChannel, const int controllerNumber, const int controllerValue) { switch (controllerNumber) { case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; default: break; } const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) voice->controllerMoved (controllerNumber, controllerValue); } } void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) { jassert (midiChannel > 0 && midiChannel <= 16); const ScopedLock sl (lock); if (isDown) { sustainPedalsDown.setBit (midiChannel); } else { for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) stopVoice (voice, true); } sustainPedalsDown.clearBit (midiChannel); } } void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) { jassert (midiChannel > 0 && midiChannel <= 16); const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->isPlayingChannel (midiChannel)) { if (isDown) voice->sostenutoPedalDown = true; else if (voice->sostenutoPedalDown) stopVoice (voice, true); } } } void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) { (void) midiChannel; jassert (midiChannel > 0 && midiChannel <= 16); } SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, const bool stealIfNoneAvailable) const { const ScopedLock sl (lock); for (int i = voices.size(); --i >= 0;) if (voices.getUnchecked (i)->getCurrentlyPlayingNote() < 0 && voices.getUnchecked (i)->canPlaySound (soundToPlay)) return voices.getUnchecked (i); if (stealIfNoneAvailable) { // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. SynthesiserVoice* oldest = nullptr; for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->canPlaySound (soundToPlay) && (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime)) oldest = voice; } jassert (oldest != nullptr); return oldest; } return nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Synthesiser.cpp ***/ /*** Start of inlined file: juce_ActionBroadcaster.cpp ***/ BEGIN_JUCE_NAMESPACE // special message of our own with a string in it class ActionMessage : public Message { public: ActionMessage (const String& messageText, ActionListener* const listener_) noexcept : message (messageText) { pointerParameter = listener_; } const String message; private: JUCE_DECLARE_NON_COPYABLE (ActionMessage); }; ActionBroadcaster::CallbackReceiver::CallbackReceiver() {} void ActionBroadcaster::CallbackReceiver::handleMessage (const Message& message) { const ActionMessage& am = static_cast (message); ActionListener* const target = static_cast (am.pointerParameter); if (owner->actionListeners.contains (target)) target->actionListenerCallback (am.message); } ActionBroadcaster::ActionBroadcaster() { // are you trying to create this object before or after juce has been intialised?? jassert (MessageManager::instance != nullptr); callback.owner = this; } ActionBroadcaster::~ActionBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != nullptr); } void ActionBroadcaster::addActionListener (ActionListener* const listener) { const ScopedLock sl (actionListenerLock); if (listener != nullptr) actionListeners.add (listener); } void ActionBroadcaster::removeActionListener (ActionListener* const listener) { const ScopedLock sl (actionListenerLock); actionListeners.removeValue (listener); } void ActionBroadcaster::removeAllActionListeners() { const ScopedLock sl (actionListenerLock); actionListeners.clear(); } void ActionBroadcaster::sendActionMessage (const String& message) const { const ScopedLock sl (actionListenerLock); for (int i = actionListeners.size(); --i >= 0;) callback.postMessage (new ActionMessage (message, actionListeners.getUnchecked(i))); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ActionBroadcaster.cpp ***/ /*** Start of inlined file: juce_AsyncUpdater.cpp ***/ BEGIN_JUCE_NAMESPACE class AsyncUpdaterMessage : public CallbackMessage { public: AsyncUpdaterMessage (AsyncUpdater& owner_) : owner (owner_) { } void messageCallback() { if (shouldDeliver.compareAndSetBool (0, 1)) owner.handleAsyncUpdate(); } Atomic shouldDeliver; private: AsyncUpdater& owner; }; AsyncUpdater::AsyncUpdater() { message = new AsyncUpdaterMessage (*this); } inline Atomic& AsyncUpdater::getDeliveryFlag() const noexcept { return static_cast (message.getObject())->shouldDeliver; } AsyncUpdater::~AsyncUpdater() { // You're deleting this object with a background thread while there's an update // pending on the main event thread - that's pretty dodgy threading, as the callback could // happen after this destructor has finished. You should either use a MessageManagerLock while // deleting this object, or find some other way to avoid such a race condition. jassert ((! isUpdatePending()) || MessageManager::getInstance()->currentThreadHasLockedMessageManager()); getDeliveryFlag().set (0); } void AsyncUpdater::triggerAsyncUpdate() { if (getDeliveryFlag().compareAndSetBool (1, 0)) message->post(); } void AsyncUpdater::cancelPendingUpdate() noexcept { getDeliveryFlag().set (0); } void AsyncUpdater::handleUpdateNowIfNeeded() { // This can only be called by the event thread. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); if (getDeliveryFlag().exchange (0) != 0) handleAsyncUpdate(); } bool AsyncUpdater::isUpdatePending() const noexcept { return getDeliveryFlag().value != 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_AsyncUpdater.cpp ***/ /*** Start of inlined file: juce_ChangeBroadcaster.cpp ***/ BEGIN_JUCE_NAMESPACE ChangeBroadcaster::ChangeBroadcaster() noexcept { // are you trying to create this object before or after juce has been intialised?? jassert (MessageManager::instance != nullptr); callback.owner = this; } ChangeBroadcaster::~ChangeBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != nullptr); } void ChangeBroadcaster::addChangeListener (ChangeListener* const listener) { // Listeners can only be safely added when the event thread is locked // You can use a MessageManagerLock if you need to call this from another thread. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); changeListeners.add (listener); } void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener) { // Listeners can only be safely added when the event thread is locked // You can use a MessageManagerLock if you need to call this from another thread. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); changeListeners.remove (listener); } void ChangeBroadcaster::removeAllChangeListeners() { // Listeners can only be safely added when the event thread is locked // You can use a MessageManagerLock if you need to call this from another thread. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); changeListeners.clear(); } void ChangeBroadcaster::sendChangeMessage() { if (changeListeners.size() > 0) callback.triggerAsyncUpdate(); } void ChangeBroadcaster::sendSynchronousChangeMessage() { // This can only be called by the event thread. jassert (MessageManager::getInstance()->isThisTheMessageThread()); callback.cancelPendingUpdate(); callListeners(); } void ChangeBroadcaster::dispatchPendingMessages() { callback.handleUpdateNowIfNeeded(); } void ChangeBroadcaster::callListeners() { changeListeners.call (&ChangeListener::changeListenerCallback, this); } ChangeBroadcaster::ChangeBroadcasterCallback::ChangeBroadcasterCallback() : owner (nullptr) { } void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate() { jassert (owner != nullptr); owner->callListeners(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ChangeBroadcaster.cpp ***/ /*** Start of inlined file: juce_InterprocessConnection.cpp ***/ BEGIN_JUCE_NAMESPACE InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, const uint32 magicMessageHeaderNumber) : Thread ("Juce IPC connection"), callbackConnectionState (false), useMessageThread (callbacksOnMessageThread), magicMessageHeader (magicMessageHeaderNumber), pipeReceiveMessageTimeout (-1) { } InterprocessConnection::~InterprocessConnection() { callbackConnectionState = false; disconnect(); } bool InterprocessConnection::connectToSocket (const String& hostName, const int portNumber, const int timeOutMillisecs) { disconnect(); const ScopedLock sl (pipeAndSocketLock); socket = new StreamingSocket(); if (socket->connect (hostName, portNumber, timeOutMillisecs)) { connectionMadeInt(); startThread(); return true; } else { socket = nullptr; return false; } } bool InterprocessConnection::connectToPipe (const String& pipeName, const int pipeReceiveMessageTimeoutMs) { disconnect(); ScopedPointer newPipe (new NamedPipe()); if (newPipe->openExisting (pipeName)) { const ScopedLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = pipeReceiveMessageTimeoutMs; initialiseWithPipe (newPipe.release()); return true; } return false; } bool InterprocessConnection::createPipe (const String& pipeName, const int pipeReceiveMessageTimeoutMs) { disconnect(); ScopedPointer newPipe (new NamedPipe()); if (newPipe->createNewPipe (pipeName)) { const ScopedLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = pipeReceiveMessageTimeoutMs; initialiseWithPipe (newPipe.release()); return true; } return false; } void InterprocessConnection::disconnect() { if (socket != nullptr) socket->close(); if (pipe != nullptr) { pipe->cancelPendingReads(); pipe->close(); } stopThread (4000); { const ScopedLock sl (pipeAndSocketLock); socket = nullptr; pipe = nullptr; } connectionLostInt(); } bool InterprocessConnection::isConnected() const { const ScopedLock sl (pipeAndSocketLock); return ((socket != nullptr && socket->isConnected()) || (pipe != nullptr && pipe->isOpen())) && isThreadRunning(); } const String InterprocessConnection::getConnectedHostName() const { if (pipe != nullptr) { return "localhost"; } else if (socket != nullptr) { if (! socket->isLocal()) return socket->getHostName(); return "localhost"; } return String::empty; } bool InterprocessConnection::sendMessage (const MemoryBlock& message) { uint32 messageHeader[2]; messageHeader [0] = ByteOrder::swapIfBigEndian (magicMessageHeader); messageHeader [1] = ByteOrder::swapIfBigEndian ((uint32) message.getSize()); MemoryBlock messageData (sizeof (messageHeader) + message.getSize()); messageData.copyFrom (messageHeader, 0, sizeof (messageHeader)); messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize()); int bytesWritten = 0; const ScopedLock sl (pipeAndSocketLock); if (socket != nullptr) bytesWritten = socket->write (messageData.getData(), (int) messageData.getSize()); else if (pipe != nullptr) bytesWritten = pipe->write (messageData.getData(), (int) messageData.getSize()); return bytesWritten == (int) messageData.getSize(); } void InterprocessConnection::initialiseWithSocket (StreamingSocket* const socket_) { jassert (socket == 0); socket = socket_; connectionMadeInt(); startThread(); } void InterprocessConnection::initialiseWithPipe (NamedPipe* const pipe_) { jassert (pipe == 0); pipe = pipe_; connectionMadeInt(); startThread(); } const int messageMagicNumber = 0xb734128b; void InterprocessConnection::handleMessage (const Message& message) { if (message.intParameter1 == messageMagicNumber) { switch (message.intParameter2) { case 0: { ScopedPointer data (static_cast (message.pointerParameter)); messageReceived (*data); break; } case 1: connectionMade(); break; case 2: connectionLost(); break; } } } void InterprocessConnection::connectionMadeInt() { if (! callbackConnectionState) { callbackConnectionState = true; if (useMessageThread) postMessage (new Message (messageMagicNumber, 1, 0, 0)); else connectionMade(); } } void InterprocessConnection::connectionLostInt() { if (callbackConnectionState) { callbackConnectionState = false; if (useMessageThread) postMessage (new Message (messageMagicNumber, 2, 0, 0)); else connectionLost(); } } void InterprocessConnection::deliverDataInt (const MemoryBlock& data) { jassert (callbackConnectionState); if (useMessageThread) postMessage (new Message (messageMagicNumber, 0, 0, new MemoryBlock (data))); else messageReceived (data); } bool InterprocessConnection::readNextMessageInt() { const int maximumMessageSize = 1024 * 1024 * 10; // sanity check uint32 messageHeader[2]; const int bytes = socket != nullptr ? socket->read (messageHeader, sizeof (messageHeader), true) : pipe ->read (messageHeader, sizeof (messageHeader), pipeReceiveMessageTimeout); if (bytes == sizeof (messageHeader) && ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader) { int bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]); if (bytesInMessage > 0 && bytesInMessage < maximumMessageSize) { MemoryBlock messageData (bytesInMessage, true); int bytesRead = 0; while (bytesInMessage > 0) { if (threadShouldExit()) return false; const int numThisTime = jmin (bytesInMessage, 65536); const int bytesIn = socket != nullptr ? socket->read (static_cast (messageData.getData()) + bytesRead, numThisTime, true) : pipe ->read (static_cast (messageData.getData()) + bytesRead, numThisTime, pipeReceiveMessageTimeout); if (bytesIn <= 0) break; bytesRead += bytesIn; bytesInMessage -= bytesIn; } if (bytesRead >= 0) deliverDataInt (messageData); } } else if (bytes < 0) { { const ScopedLock sl (pipeAndSocketLock); socket = nullptr; } connectionLostInt(); return false; } return true; } void InterprocessConnection::run() { while (! threadShouldExit()) { if (socket != nullptr) { const int ready = socket->waitUntilReady (true, 0); if (ready < 0) { { const ScopedLock sl (pipeAndSocketLock); socket = nullptr; } connectionLostInt(); break; } else if (ready > 0) { if (! readNextMessageInt()) break; } else { Thread::sleep (2); } } else if (pipe != nullptr) { if (! pipe->isOpen()) { { const ScopedLock sl (pipeAndSocketLock); pipe = nullptr; } connectionLostInt(); break; } else { if (! readNextMessageInt()) break; } } else { break; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InterprocessConnection.cpp ***/ /*** Start of inlined file: juce_InterprocessConnectionServer.cpp ***/ BEGIN_JUCE_NAMESPACE InterprocessConnectionServer::InterprocessConnectionServer() : Thread ("Juce IPC server") { } InterprocessConnectionServer::~InterprocessConnectionServer() { stop(); } bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber) { stop(); socket = new StreamingSocket(); if (socket->createListener (portNumber)) { startThread(); return true; } socket = nullptr; return false; } void InterprocessConnectionServer::stop() { signalThreadShouldExit(); if (socket != nullptr) socket->close(); stopThread (4000); socket = nullptr; } void InterprocessConnectionServer::run() { while ((! threadShouldExit()) && socket != nullptr) { ScopedPointer clientSocket (socket->waitForNextConnection()); if (clientSocket != nullptr) { InterprocessConnection* newConnection = createConnectionObject(); if (newConnection != nullptr) newConnection->initialiseWithSocket (clientSocket.release()); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_InterprocessConnectionServer.cpp ***/ /*** Start of inlined file: juce_Message.cpp ***/ BEGIN_JUCE_NAMESPACE Message::Message() noexcept : intParameter1 (0), intParameter2 (0), intParameter3 (0), pointerParameter (nullptr), messageRecipient (nullptr) { } Message::Message (const int intParameter1_, const int intParameter2_, const int intParameter3_, void* const pointerParameter_) noexcept : intParameter1 (intParameter1_), intParameter2 (intParameter2_), intParameter3 (intParameter3_), pointerParameter (pointerParameter_), messageRecipient (nullptr) { } Message::~Message() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Message.cpp ***/ /*** Start of inlined file: juce_MessageListener.cpp ***/ BEGIN_JUCE_NAMESPACE MessageListener::MessageListener() noexcept { // are you trying to create a messagelistener before or after juce has been intialised?? jassert (MessageManager::instance != nullptr); if (MessageManager::instance != nullptr) MessageManager::instance->messageListeners.add (this); } MessageListener::~MessageListener() { if (MessageManager::instance != nullptr) MessageManager::instance->messageListeners.removeValue (this); } void MessageListener::postMessage (Message* const message) const noexcept { message->messageRecipient = const_cast (this); if (MessageManager::instance == nullptr) MessageManager::getInstance(); MessageManager::instance->postMessageToQueue (message); } bool MessageListener::isValidMessageListener() const noexcept { return (MessageManager::instance != nullptr) && MessageManager::instance->messageListeners.contains (this); } END_JUCE_NAMESPACE /*** End of inlined file: juce_MessageListener.cpp ***/ /*** Start of inlined file: juce_MessageManager.cpp ***/ BEGIN_JUCE_NAMESPACE MessageManager* MessageManager::instance = nullptr; enum { quitMessageId = 0xfffff321 }; MessageManager::MessageManager() noexcept : quitMessagePosted (false), quitMessageReceived (false), messageThreadId (Thread::getCurrentThreadId()), threadWithLock (0) { if (JUCEApplication::isStandaloneApp()) Thread::setCurrentThreadName ("Juce Message Thread"); } MessageManager::~MessageManager() noexcept { broadcaster = nullptr; doPlatformSpecificShutdown(); // If you hit this assertion, then you've probably leaked some kind of MessageListener object.. jassert (messageListeners.size() == 0); jassert (instance == this); instance = nullptr; // do this last in case this instance is still needed by doPlatformSpecificShutdown() } MessageManager* MessageManager::getInstance() noexcept { if (instance == nullptr) { instance = new MessageManager(); doPlatformSpecificInitialisation(); } return instance; } void MessageManager::postMessageToQueue (Message* const message) { if (quitMessagePosted || ! postMessageToSystemQueue (message)) Message::Ptr deleter (message); // (this will delete messages that were just created with a 0 ref count) } CallbackMessage::CallbackMessage() noexcept {} CallbackMessage::~CallbackMessage() {} void CallbackMessage::post() { if (MessageManager::instance != nullptr) MessageManager::instance->postMessageToQueue (this); } // not for public use.. void MessageManager::deliverMessage (Message* const message) { JUCE_TRY { MessageListener* const recipient = message->messageRecipient; if (recipient == nullptr) { CallbackMessage* const callbackMessage = dynamic_cast (message); if (callbackMessage != nullptr) { callbackMessage->messageCallback(); } else if (message->intParameter1 == (int) quitMessageId) { quitMessageReceived = true; } } else if (messageListeners.contains (recipient)) { recipient->handleMessage (*message); } } JUCE_CATCH_EXCEPTION } #if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS) void MessageManager::runDispatchLoop() { jassert (isThisTheMessageThread()); // must only be called by the message thread runDispatchLoopUntil (-1); } void MessageManager::stopDispatchLoop() { postMessageToQueue (new Message ((int) quitMessageId, 0, 0, nullptr)); quitMessagePosted = true; } bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) { jassert (isThisTheMessageThread()); // must only be called by the message thread const int64 endTime = Time::currentTimeMillis() + millisecondsToRunFor; while ((millisecondsToRunFor < 0 || endTime > Time::currentTimeMillis()) && ! quitMessageReceived) { JUCE_TRY { if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) { const int msToWait = (int) (endTime - Time::currentTimeMillis()); if (msToWait > 0) Thread::sleep (jmin (5, msToWait)); } } JUCE_CATCH_EXCEPTION } return ! quitMessageReceived; } #endif void MessageManager::deliverBroadcastMessage (const String& value) { if (broadcaster != nullptr) broadcaster->sendActionMessage (value); } void MessageManager::registerBroadcastListener (ActionListener* const listener) { if (broadcaster == nullptr) broadcaster = new ActionBroadcaster(); broadcaster->addActionListener (listener); } void MessageManager::deregisterBroadcastListener (ActionListener* const listener) { if (broadcaster != nullptr) broadcaster->removeActionListener (listener); } bool MessageManager::isThisTheMessageThread() const noexcept { return Thread::getCurrentThreadId() == messageThreadId; } void MessageManager::setCurrentThreadAsMessageThread() { const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); if (messageThreadId != thisThread) { messageThreadId = thisThread; // This is needed on windows to make sure the message window is created by this thread doPlatformSpecificShutdown(); doPlatformSpecificInitialisation(); } } bool MessageManager::currentThreadHasLockedMessageManager() const noexcept { const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); return thisThread == messageThreadId || thisThread == threadWithLock; } /* The only safe way to lock the message thread while another thread does some work is by posting a special message, whose purpose is to tie up the event loop until the other thread has finished its business. Any other approach can get horribly deadlocked if the OS uses its own hidden locks which get locked before making an event callback, because if the same OS lock gets indirectly accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens in Cocoa). */ class MessageManagerLock::BlockingMessage : public CallbackMessage { public: BlockingMessage() {} void messageCallback() { lockedEvent.signal(); releaseEvent.wait(); } WaitableEvent lockedEvent, releaseEvent; private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockingMessage); }; MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) : locked (false) { init (threadToCheck, nullptr); } MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) : locked (false) { init (nullptr, jobToCheckForExitSignal); } void MessageManagerLock::init (Thread* const threadToCheck, ThreadPoolJob* const job) { if (MessageManager::instance != nullptr) { if (MessageManager::instance->currentThreadHasLockedMessageManager()) { locked = true; // either we're on the message thread, or this is a re-entrant call. } else { if (threadToCheck == nullptr && job == nullptr) { MessageManager::instance->lockingLock.enter(); } else { while (! MessageManager::instance->lockingLock.tryEnter()) { if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) || (job != nullptr && job->shouldExit())) return; Thread::sleep (1); } } blockingMessage = new BlockingMessage(); blockingMessage->post(); while (! blockingMessage->lockedEvent.wait (20)) { if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) || (job != nullptr && job->shouldExit())) { blockingMessage->releaseEvent.signal(); blockingMessage = nullptr; MessageManager::instance->lockingLock.exit(); return; } } jassert (MessageManager::instance->threadWithLock == 0); MessageManager::instance->threadWithLock = Thread::getCurrentThreadId(); locked = true; } } } MessageManagerLock::~MessageManagerLock() noexcept { if (blockingMessage != nullptr) { jassert (MessageManager::instance == nullptr || MessageManager::instance->currentThreadHasLockedMessageManager()); blockingMessage->releaseEvent.signal(); blockingMessage = nullptr; if (MessageManager::instance != nullptr) { MessageManager::instance->threadWithLock = 0; MessageManager::instance->lockingLock.exit(); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_MessageManager.cpp ***/ /*** Start of inlined file: juce_MultiTimer.cpp ***/ BEGIN_JUCE_NAMESPACE class MultiTimer::MultiTimerCallback : public Timer { public: MultiTimerCallback (const int timerId_, MultiTimer& owner_) : timerId (timerId_), owner (owner_) { } void timerCallback() { owner.timerCallback (timerId); } const int timerId; private: MultiTimer& owner; }; MultiTimer::MultiTimer() noexcept { } MultiTimer::MultiTimer (const MultiTimer&) noexcept { } MultiTimer::~MultiTimer() { const SpinLock::ScopedLockType sl (timerListLock); timers.clear(); } void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds) noexcept { const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) { t->startTimer (intervalInMilliseconds); return; } } MultiTimerCallback* const newTimer = new MultiTimerCallback (timerId, *this); timers.add (newTimer); newTimer->startTimer (intervalInMilliseconds); } void MultiTimer::stopTimer (const int timerId) noexcept { const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) t->stopTimer(); } } bool MultiTimer::isTimerRunning (const int timerId) const noexcept { const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { const MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) return t->isTimerRunning(); } return false; } int MultiTimer::getTimerInterval (const int timerId) const noexcept { const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { const MultiTimerCallback* const t = timers.getUnchecked(i); if (t->timerId == timerId) return t->getTimerInterval(); } return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_MultiTimer.cpp ***/ /*** Start of inlined file: juce_Timer.cpp ***/ BEGIN_JUCE_NAMESPACE class InternalTimerThread : private Thread, private MessageListener, private DeletedAtShutdown, private AsyncUpdater { public: typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..) InternalTimerThread() : Thread ("Juce Timer"), firstTimer (nullptr), callbackNeeded (0) { triggerAsyncUpdate(); } ~InternalTimerThread() noexcept { stopThread (4000); jassert (instance == this || instance == nullptr); if (instance == this) instance = nullptr; } void run() { uint32 lastTime = Time::getMillisecondCounter(); Message::Ptr messageToSend (new Message()); while (! threadShouldExit()) { const uint32 now = Time::getMillisecondCounter(); if (now == lastTime) { wait (1); continue; } const int elapsed = now >= lastTime ? (now - lastTime) : (std::numeric_limits::max() - (lastTime - now)); lastTime = now; const int timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed); if (timeUntilFirstTimer <= 0) { /* If we managed to set the atomic boolean to true then send a message, this is needed as a memory barrier so the message won't be sent before callbackNeeded is set to true, but if it fails it means the message-thread changed the value from under us so at least some processing is happenening and we can just loop around and try again */ if (callbackNeeded.compareAndSetBool (1, 0)) { postMessage (messageToSend); /* Sometimes our message can get discarded by the OS (e.g. when running as an RTAS when the app has a modal loop), so this is how long to wait before assuming the message has been lost and trying again. */ const uint32 messageDeliveryTimeout = now + 2000; while (callbackNeeded.get() != 0) { wait (4); if (threadShouldExit()) return; if (Time::getMillisecondCounter() > messageDeliveryTimeout) break; } } } else { // don't wait for too long because running this loop also helps keep the // Time::getApproximateMillisecondTimer value stay up-to-date wait (jlimit (1, 50, timeUntilFirstTimer)); } } } void callTimers() { const LockType::ScopedLockType sl (lock); while (firstTimer != nullptr && firstTimer->countdownMs <= 0) { Timer* const t = firstTimer; t->countdownMs = t->periodMs; removeTimer (t); addTimer (t); const LockType::ScopedUnlockType ul (lock); JUCE_TRY { t->timerCallback(); } JUCE_CATCH_EXCEPTION } /* This is needed as a memory barrier to make sure all processing of current timers is done before the boolean is set. This set should never fail since if it was false in the first place, we wouldn't get a message (so it can't be changed from false to true from under us), and if we get a message then the value is true and the other thread can only set it to true again and we will get another callback to set it to false. */ callbackNeeded.set (0); } void handleMessage (const Message&) { callTimers(); } void callTimersSynchronously() { if (! isThreadRunning()) { // (This is relied on by some plugins in cases where the MM has // had to restart and the async callback never started) cancelPendingUpdate(); triggerAsyncUpdate(); } callTimers(); } static void callAnyTimersSynchronously() { if (InternalTimerThread::instance != nullptr) InternalTimerThread::instance->callTimersSynchronously(); } static inline void add (Timer* const tim) noexcept { if (instance == nullptr) instance = new InternalTimerThread(); instance->addTimer (tim); } static inline void remove (Timer* const tim) noexcept { if (instance != nullptr) instance->removeTimer (tim); } static inline void resetCounter (Timer* const tim, const int newCounter) noexcept { if (instance != nullptr) { tim->countdownMs = newCounter; tim->periodMs = newCounter; if ((tim->next != nullptr && tim->next->countdownMs < tim->countdownMs) || (tim->previous != nullptr && tim->previous->countdownMs > tim->countdownMs)) { instance->removeTimer (tim); instance->addTimer (tim); } } } private: friend class Timer; static InternalTimerThread* instance; static LockType lock; Timer* volatile firstTimer; Atomic callbackNeeded; void addTimer (Timer* const t) noexcept { #if JUCE_DEBUG Timer* tt = firstTimer; while (tt != nullptr) { // trying to add a timer that's already here - shouldn't get to this point, // so if you get this assertion, let me know! jassert (tt != t); tt = tt->next; } jassert (t->previous == nullptr && t->next == nullptr); #endif Timer* i = firstTimer; if (i == nullptr || i->countdownMs > t->countdownMs) { t->next = firstTimer; firstTimer = t; } else { while (i->next != nullptr && i->next->countdownMs <= t->countdownMs) i = i->next; jassert (i != nullptr); t->next = i->next; t->previous = i; i->next = t; } if (t->next != nullptr) t->next->previous = t; jassert ((t->next == nullptr || t->next->countdownMs >= t->countdownMs) && (t->previous == nullptr || t->previous->countdownMs <= t->countdownMs)); notify(); } void removeTimer (Timer* const t) noexcept { #if JUCE_DEBUG Timer* tt = firstTimer; bool found = false; while (tt != nullptr) { if (tt == t) { found = true; break; } tt = tt->next; } // trying to remove a timer that's not here - shouldn't get to this point, // so if you get this assertion, let me know! jassert (found); #endif if (t->previous != nullptr) { jassert (firstTimer != t); t->previous->next = t->next; } else { jassert (firstTimer == t); firstTimer = t->next; } if (t->next != nullptr) t->next->previous = t->previous; t->next = nullptr; t->previous = nullptr; } int getTimeUntilFirstTimer (const int numMillisecsElapsed) const { const LockType::ScopedLockType sl (lock); for (Timer* t = firstTimer; t != nullptr; t = t->next) t->countdownMs -= numMillisecsElapsed; return firstTimer != nullptr ? firstTimer->countdownMs : 1000; } void handleAsyncUpdate() { startThread (7); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalTimerThread); }; InternalTimerThread* InternalTimerThread::instance = nullptr; InternalTimerThread::LockType InternalTimerThread::lock; void juce_callAnyTimersSynchronously() { InternalTimerThread::callAnyTimersSynchronously(); } #if JUCE_DEBUG static SortedSet activeTimers; #endif Timer::Timer() noexcept : countdownMs (0), periodMs (0), previous (nullptr), next (nullptr) { #if JUCE_DEBUG const InternalTimerThread::LockType::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } Timer::Timer (const Timer&) noexcept : countdownMs (0), periodMs (0), previous (nullptr), next (nullptr) { #if JUCE_DEBUG const InternalTimerThread::LockType::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } Timer::~Timer() { stopTimer(); #if JUCE_DEBUG activeTimers.removeValue (this); #endif } void Timer::startTimer (const int interval) noexcept { const InternalTimerThread::LockType::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. jassert (activeTimers.contains (this)); #endif if (periodMs == 0) { countdownMs = interval; periodMs = jmax (1, interval); InternalTimerThread::add (this); } else { InternalTimerThread::resetCounter (this, interval); } } void Timer::stopTimer() noexcept { const InternalTimerThread::LockType::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. jassert (activeTimers.contains (this)); #endif if (periodMs > 0) { InternalTimerThread::remove (this); periodMs = 0; } } END_JUCE_NAMESPACE /*** End of inlined file: juce_Timer.cpp ***/ #endif #if JUCE_BUILD_GUI /*** Start of inlined file: juce_Component.cpp ***/ BEGIN_JUCE_NAMESPACE #define CHECK_MESSAGE_MANAGER_IS_LOCKED jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); Component* Component::currentlyFocusedComponent = nullptr; class Component::MouseListenerList { public: MouseListenerList() : numDeepMouseListeners (0) { } void addListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) { if (! listeners.contains (newListener)) { if (wantsEventsForAllNestedChildComponents) { listeners.insert (0, newListener); ++numDeepMouseListeners; } else { listeners.add (newListener); } } } void removeListener (MouseListener* const listenerToRemove) { const int index = listeners.indexOf (listenerToRemove); if (index >= 0) { if (index < numDeepMouseListeners) --numDeepMouseListeners; listeners.remove (index); } } static void sendMouseEvent (Component& comp, BailOutChecker& checker, void (MouseListener::*eventMethod) (const MouseEvent&), const MouseEvent& e) { if (checker.shouldBailOut()) return; { MouseListenerList* const list = comp.mouseListeners; if (list != nullptr) { for (int i = list->listeners.size(); --i >= 0;) { (list->listeners.getUnchecked(i)->*eventMethod) (e); if (checker.shouldBailOut()) return; i = jmin (i, list->listeners.size()); } } } Component* p = comp.parentComponent; while (p != nullptr) { MouseListenerList* const list = p->mouseListeners; if (list != nullptr && list->numDeepMouseListeners > 0) { BailOutChecker2 checker2 (checker, p); for (int i = list->numDeepMouseListeners; --i >= 0;) { (list->listeners.getUnchecked(i)->*eventMethod) (e); if (checker2.shouldBailOut()) return; i = jmin (i, list->numDeepMouseListeners); } } p = p->parentComponent; } } static void sendWheelEvent (Component& comp, BailOutChecker& checker, const MouseEvent& e, const float wheelIncrementX, const float wheelIncrementY) { { MouseListenerList* const list = comp.mouseListeners; if (list != nullptr) { for (int i = list->listeners.size(); --i >= 0;) { list->listeners.getUnchecked(i)->mouseWheelMove (e, wheelIncrementX, wheelIncrementY); if (checker.shouldBailOut()) return; i = jmin (i, list->listeners.size()); } } } Component* p = comp.parentComponent; while (p != nullptr) { MouseListenerList* const list = p->mouseListeners; if (list != nullptr && list->numDeepMouseListeners > 0) { BailOutChecker2 checker2 (checker, p); for (int i = list->numDeepMouseListeners; --i >= 0;) { list->listeners.getUnchecked(i)->mouseWheelMove (e, wheelIncrementX, wheelIncrementY); if (checker2.shouldBailOut()) return; i = jmin (i, list->numDeepMouseListeners); } } p = p->parentComponent; } } private: Array listeners; int numDeepMouseListeners; class BailOutChecker2 { public: BailOutChecker2 (BailOutChecker& checker_, Component* const component) : checker (checker_), safePointer (component) { } bool shouldBailOut() const noexcept { return checker.shouldBailOut() || safePointer == 0; } private: BailOutChecker& checker; const WeakReference safePointer; JUCE_DECLARE_NON_COPYABLE (BailOutChecker2); }; JUCE_DECLARE_NON_COPYABLE (MouseListenerList); }; class Component::ComponentHelpers { public: #if JUCE_MODAL_LOOPS_PERMITTED static void* runModalLoopCallback (void* userData) { return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); } #endif static const Identifier getColourPropertyId (const int colourId) { String s; s.preallocateBytes (32); s << "jcclr_" << String::toHexString (colourId); return s; } static inline bool hitTest (Component& comp, const Point& localPoint) { return isPositiveAndBelow (localPoint.getX(), comp.getWidth()) && isPositiveAndBelow (localPoint.getY(), comp.getHeight()) && comp.hitTest (localPoint.getX(), localPoint.getY()); } static const Point convertFromParentSpace (const Component& comp, const Point& pointInParentSpace) { if (comp.affineTransform == nullptr) return pointInParentSpace - comp.getPosition(); return pointInParentSpace.toFloat().transformedBy (comp.affineTransform->inverted()).toInt() - comp.getPosition(); } static const Rectangle convertFromParentSpace (const Component& comp, const Rectangle& areaInParentSpace) { if (comp.affineTransform == nullptr) return areaInParentSpace - comp.getPosition(); return areaInParentSpace.toFloat().transformed (comp.affineTransform->inverted()).getSmallestIntegerContainer() - comp.getPosition(); } static const Point convertToParentSpace (const Component& comp, const Point& pointInLocalSpace) { if (comp.affineTransform == nullptr) return pointInLocalSpace + comp.getPosition(); return (pointInLocalSpace + comp.getPosition()).toFloat().transformedBy (*comp.affineTransform).toInt(); } static const Rectangle convertToParentSpace (const Component& comp, const Rectangle& areaInLocalSpace) { if (comp.affineTransform == nullptr) return areaInLocalSpace + comp.getPosition(); return (areaInLocalSpace + comp.getPosition()).toFloat().transformed (*comp.affineTransform).getSmallestIntegerContainer(); } template static const Type convertFromDistantParentSpace (const Component* parent, const Component& target, Type coordInParent) { const Component* const directParent = target.getParentComponent(); jassert (directParent != nullptr); if (directParent == parent) return convertFromParentSpace (target, coordInParent); return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent)); } template static const Type convertCoordinate (const Component* target, const Component* source, Type p) { while (source != nullptr) { if (source == target) return p; if (source->isParentOf (target)) return convertFromDistantParentSpace (source, *target, p); if (source->isOnDesktop()) { p = source->getPeer()->localToGlobal (p); source = nullptr; } else { p = convertToParentSpace (*source, p); source = source->getParentComponent(); } } jassert (source == nullptr); if (target == nullptr) return p; const Component* const topLevelComp = target->getTopLevelComponent(); if (topLevelComp->isOnDesktop()) p = topLevelComp->getPeer()->globalToLocal (p); else p = convertFromParentSpace (*topLevelComp, p); if (topLevelComp == target) return p; return convertFromDistantParentSpace (topLevelComp, *target, p); } static const Rectangle getUnclippedArea (const Component& comp) { Rectangle r (comp.getLocalBounds()); Component* const p = comp.getParentComponent(); if (p != nullptr) r = r.getIntersection (convertFromParentSpace (comp, getUnclippedArea (*p))); return r; } static void clipObscuredRegions (const Component& comp, Graphics& g, const Rectangle& clipRect, const Point& delta) { for (int i = comp.childComponentList.size(); --i >= 0;) { const Component& child = *comp.childComponentList.getUnchecked(i); if (child.isVisible() && ! child.isTransformed()) { const Rectangle newClip (clipRect.getIntersection (child.bounds)); if (! newClip.isEmpty()) { if (child.isOpaque()) { g.excludeClipRegion (newClip + delta); } else { const Point childPos (child.getPosition()); clipObscuredRegions (child, g, newClip - childPos, childPos + delta); } } } } } static void subtractObscuredRegions (const Component& comp, RectangleList& result, const Point& delta, const Rectangle& clipRect, const Component* const compToAvoid) { for (int i = comp.childComponentList.size(); --i >= 0;) { const Component* const c = comp.childComponentList.getUnchecked(i); if (c != compToAvoid && c->isVisible()) { if (c->isOpaque()) { Rectangle childBounds (c->bounds.getIntersection (clipRect)); childBounds.translate (delta.getX(), delta.getY()); result.subtract (childBounds); } else { Rectangle newClip (clipRect.getIntersection (c->bounds)); newClip.translate (-c->getX(), -c->getY()); subtractObscuredRegions (*c, result, c->getPosition() + delta, newClip, compToAvoid); } } } } static const Rectangle getParentOrMainMonitorBounds (const Component& comp) { return comp.getParentComponent() != nullptr ? comp.getParentComponent()->getLocalBounds() : Desktop::getInstance().getMainMonitorArea(); } }; Component::Component() : parentComponent (nullptr), lookAndFeel (nullptr), effect (nullptr), componentFlags (0), componentTransparency (0) { } Component::Component (const String& name) : componentName (name), parentComponent (nullptr), lookAndFeel (nullptr), effect (nullptr), componentFlags (0), componentTransparency (0) { } Component::~Component() { #if ! JUCE_VC6 // (access to private union not allowed in VC6) static_jassert (sizeof (flags) <= sizeof (componentFlags)); #endif componentListeners.call (&ComponentListener::componentBeingDeleted, *this); weakReferenceMaster.clear(); while (childComponentList.size() > 0) removeChildComponent (childComponentList.size() - 1, false, true); if (parentComponent != nullptr) parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false); else if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) giveAwayFocus (currentlyFocusedComponent != this); if (flags.hasHeavyweightPeerFlag) removeFromDesktop(); // Something has added some children to this component during its destructor! Not a smart idea! jassert (childComponentList.size() == 0); } const WeakReference::SharedRef& Component::getWeakReference() { return weakReferenceMaster (this); } void Component::setName (const String& name) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (componentName != name) { componentName = name; if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); jassert (peer != nullptr); if (peer != nullptr) peer->setTitle (name); } BailOutChecker checker (this); componentListeners.callChecked (checker, &ComponentListener::componentNameChanged, *this); } } void Component::setComponentID (const String& newID) { componentID = newID; } void Component::setVisible (bool shouldBeVisible) { if (flags.visibleFlag != shouldBeVisible) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED WeakReference safePointer (this); flags.visibleFlag = shouldBeVisible; internalRepaint (0, 0, getWidth(), getHeight()); sendFakeMouseMove(); if (! shouldBeVisible) { if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) { if (parentComponent != nullptr) parentComponent->grabKeyboardFocus(); else giveAwayFocus (true); } } if (safePointer != nullptr) { sendVisibilityChangeMessage(); if (safePointer != nullptr && flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); jassert (peer != nullptr); if (peer != nullptr) { peer->setVisible (shouldBeVisible); internalHierarchyChanged(); } } } } } void Component::visibilityChanged() { } void Component::sendVisibilityChangeMessage() { BailOutChecker checker (this); visibilityChanged(); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentVisibilityChanged, *this); } bool Component::isShowing() const { if (flags.visibleFlag) { if (parentComponent != nullptr) { return parentComponent->isShowing(); } else { const ComponentPeer* const peer = getPeer(); return peer != nullptr && ! peer->isMinimised(); } } return false; } void* Component::getWindowHandle() const { const ComponentPeer* const peer = getPeer(); if (peer != nullptr) return peer->getNativeHandle(); return nullptr; } void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (isOpaque()) styleWanted &= ~ComponentPeer::windowIsSemiTransparent; else styleWanted |= ComponentPeer::windowIsSemiTransparent; int currentStyleFlags = 0; // don't use getPeer(), so that we only get the peer that's specifically // for this comp, and not for one of its parents. ComponentPeer* peer = ComponentPeer::getPeerFor (this); if (peer != nullptr) currentStyleFlags = peer->getStyleFlags(); if (styleWanted != currentStyleFlags || ! flags.hasHeavyweightPeerFlag) { WeakReference safePointer (this); #if JUCE_LINUX // it's wise to give the component a non-zero size before // putting it on the desktop, as X windows get confused by this, and // a (1, 1) minimum size is enforced here. setSize (jmax (1, getWidth()), jmax (1, getHeight())); #endif const Point topLeft (getScreenPosition()); bool wasFullscreen = false; bool wasMinimised = false; ComponentBoundsConstrainer* currentConstainer = nullptr; Rectangle oldNonFullScreenBounds; if (peer != nullptr) { wasFullscreen = peer->isFullScreen(); wasMinimised = peer->isMinimised(); currentConstainer = peer->getConstrainer(); oldNonFullScreenBounds = peer->getNonFullScreenBounds(); removeFromDesktop(); setTopLeftPosition (topLeft.getX(), topLeft.getY()); } if (parentComponent != nullptr) parentComponent->removeChildComponent (this); if (safePointer != nullptr) { flags.hasHeavyweightPeerFlag = true; peer = createNewPeer (styleWanted, nativeWindowToAttachTo); Desktop::getInstance().addDesktopComponent (this); bounds.setPosition (topLeft); peer->setBounds (topLeft.getX(), topLeft.getY(), getWidth(), getHeight(), false); peer->setVisible (isVisible()); if (wasFullscreen) { peer->setFullScreen (true); peer->setNonFullScreenBounds (oldNonFullScreenBounds); } if (wasMinimised) peer->setMinimised (true); if (isAlwaysOnTop()) peer->setAlwaysOnTop (true); peer->setConstrainer (currentConstainer); repaint(); } internalHierarchyChanged(); } } void Component::removeFromDesktop() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = ComponentPeer::getPeerFor (this); flags.hasHeavyweightPeerFlag = false; jassert (peer != nullptr); delete peer; Desktop::getInstance().removeDesktopComponent (this); } } bool Component::isOnDesktop() const noexcept { return flags.hasHeavyweightPeerFlag; } void Component::userTriedToCloseWindow() { /* This means that the user's trying to get rid of your window with the 'close window' system menu option (on windows) or possibly the task manager - you should really handle this and delete or hide your component in an appropriate way. If you want to ignore the event and don't want to trigger this assertion, just override this method and do nothing. */ jassertfalse; } void Component::minimisationStateChanged (bool) { } void Component::setOpaque (const bool shouldBeOpaque) { if (shouldBeOpaque != flags.opaqueFlag) { flags.opaqueFlag = shouldBeOpaque; if (flags.hasHeavyweightPeerFlag) { const ComponentPeer* const peer = ComponentPeer::getPeerFor (this); if (peer != nullptr) { // to make it recreate the heavyweight window addToDesktop (peer->getStyleFlags()); } } repaint(); } } bool Component::isOpaque() const noexcept { return flags.opaqueFlag; } void Component::setBufferedToImage (const bool shouldBeBuffered) { if (shouldBeBuffered != flags.bufferToImageFlag) { bufferedImage = Image::null; flags.bufferToImageFlag = shouldBeBuffered; } } void Component::moveChildInternal (const int sourceIndex, const int destIndex) { if (sourceIndex != destIndex) { Component* const c = childComponentList.getUnchecked (sourceIndex); jassert (c != nullptr); c->repaintParent(); childComponentList.move (sourceIndex, destIndex); sendFakeMouseMove(); internalChildrenChanged(); } } void Component::toFront (const bool setAsForeground) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) { peer->toFront (setAsForeground); if (setAsForeground && ! hasKeyboardFocus (true)) grabKeyboardFocus(); } } else if (parentComponent != nullptr) { const Array& childList = parentComponent->childComponentList; if (childList.getLast() != this) { const int index = childList.indexOf (this); if (index >= 0) { int insertIndex = -1; if (! flags.alwaysOnTopFlag) { insertIndex = childList.size() - 1; while (insertIndex > 0 && childList.getUnchecked (insertIndex)->isAlwaysOnTop()) --insertIndex; } parentComponent->moveChildInternal (index, insertIndex); } } if (setAsForeground) { internalBroughtToFront(); grabKeyboardFocus(); } } } void Component::toBehind (Component* const other) { if (other != nullptr && other != this) { // the two components must belong to the same parent.. jassert (parentComponent == other->parentComponent); if (parentComponent != nullptr) { const Array& childList = parentComponent->childComponentList; const int index = childList.indexOf (this); if (index >= 0 && childList [index + 1] != other) { int otherIndex = childList.indexOf (other); if (otherIndex >= 0) { if (index < otherIndex) --otherIndex; parentComponent->moveChildInternal (index, otherIndex); } } } else if (isOnDesktop()) { jassert (other->isOnDesktop()); if (other->isOnDesktop()) { ComponentPeer* const us = getPeer(); ComponentPeer* const them = other->getPeer(); jassert (us != nullptr && them != nullptr); if (us != nullptr && them != nullptr) us->toBehind (them); } } } } void Component::toBack() { if (isOnDesktop()) { jassertfalse; //xxx need to add this to native window } else if (parentComponent != nullptr) { const Array& childList = parentComponent->childComponentList; if (childList.getFirst() != this) { const int index = childList.indexOf (this); if (index > 0) { int insertIndex = 0; if (flags.alwaysOnTopFlag) while (insertIndex < childList.size() && ! childList.getUnchecked (insertIndex)->isAlwaysOnTop()) ++insertIndex; parentComponent->moveChildInternal (index, insertIndex); } } } } void Component::setAlwaysOnTop (const bool shouldStayOnTop) { if (shouldStayOnTop != flags.alwaysOnTopFlag) { BailOutChecker checker (this); flags.alwaysOnTopFlag = shouldStayOnTop; if (isOnDesktop()) { ComponentPeer* const peer = getPeer(); jassert (peer != nullptr); if (peer != nullptr) { if (! peer->setAlwaysOnTop (shouldStayOnTop)) { // some kinds of peer can't change their always-on-top status, so // for these, we'll need to create a new window const int oldFlags = peer->getStyleFlags(); removeFromDesktop(); addToDesktop (oldFlags); } } } if (shouldStayOnTop && ! checker.shouldBailOut()) toFront (false); if (! checker.shouldBailOut()) internalHierarchyChanged(); } } bool Component::isAlwaysOnTop() const noexcept { return flags.alwaysOnTopFlag; } int Component::proportionOfWidth (const float proportion) const noexcept { return roundToInt (proportion * bounds.getWidth()); } int Component::proportionOfHeight (const float proportion) const noexcept { return roundToInt (proportion * bounds.getHeight()); } int Component::getParentWidth() const noexcept { return parentComponent != nullptr ? parentComponent->getWidth() : getParentMonitorArea().getWidth(); } int Component::getParentHeight() const noexcept { return parentComponent != nullptr ? parentComponent->getHeight() : getParentMonitorArea().getHeight(); } int Component::getScreenX() const { return getScreenPosition().getX(); } int Component::getScreenY() const { return getScreenPosition().getY(); } Point Component::getScreenPosition() const { return localPointToGlobal (Point()); } Rectangle Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } Point Component::getLocalPoint (const Component* source, const Point& point) const { return ComponentHelpers::convertCoordinate (this, source, point); } Rectangle Component::getLocalArea (const Component* source, const Rectangle& area) const { return ComponentHelpers::convertCoordinate (this, source, area); } Point Component::localPointToGlobal (const Point& point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } Rectangle Component::localAreaToGlobal (const Rectangle& area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } /* Deprecated methods... */ const Point Component::relativePositionToGlobal (const Point& relativePosition) const { return localPointToGlobal (relativePosition); } const Point Component::globalPositionToRelative (const Point& screenPosition) const { return getLocalPoint (nullptr, screenPosition); } const Point Component::relativePositionToOtherComponent (const Component* const targetComponent, const Point& positionRelativeToThis) const { return targetComponent == nullptr ? localPointToGlobal (positionRelativeToThis) : targetComponent->getLocalPoint (this, positionRelativeToThis); } void Component::setBounds (const int x, const int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (w < 0) w = 0; if (h < 0) h = 0; const bool wasResized = (getWidth() != w || getHeight() != h); const bool wasMoved = (getX() != x || getY() != y); #if JUCE_DEBUG // It's a very bad idea to try to resize a window during its paint() method! jassert (! (flags.isInsidePaintCall && wasResized && isOnDesktop())); #endif if (wasMoved || wasResized) { const bool showing = isShowing(); if (showing) { // send a fake mouse move to trigger enter/exit messages if needed.. sendFakeMouseMove(); if (! flags.hasHeavyweightPeerFlag) repaintParent(); } bounds.setBounds (x, y, w, h); if (showing) { if (wasResized) repaint(); else if (! flags.hasHeavyweightPeerFlag) repaintParent(); } else { bufferedImage = Image::null; } if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) { if (wasMoved && wasResized) peer->setBounds (getX(), getY(), getWidth(), getHeight(), false); else if (wasMoved) peer->setPosition (getX(), getY()); else if (wasResized) peer->setSize (getWidth(), getHeight()); } } sendMovedResizedMessages (wasMoved, wasResized); } } void Component::sendMovedResizedMessages (const bool wasMoved, const bool wasResized) { BailOutChecker checker (this); if (wasMoved) { moved(); if (checker.shouldBailOut()) return; } if (wasResized) { resized(); if (checker.shouldBailOut()) return; for (int i = childComponentList.size(); --i >= 0;) { childComponentList.getUnchecked(i)->parentSizeChanged(); if (checker.shouldBailOut()) return; i = jmin (i, childComponentList.size()); } } if (parentComponent != nullptr) parentComponent->childBoundsChanged (this); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentMovedOrResized, *this, wasMoved, wasResized); } void Component::setSize (const int w, const int h) { setBounds (getX(), getY(), w, h); } void Component::setTopLeftPosition (const int x, const int y) { setBounds (x, y, getWidth(), getHeight()); } void Component::setTopRightPosition (const int x, const int y) { setTopLeftPosition (x - getWidth(), y); } void Component::setBounds (const Rectangle& r) { setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } void Component::setBounds (const RelativeRectangle& newBounds) { newBounds.applyToComponent (*this); } void Component::setBounds (const String& newBoundsExpression) { setBounds (RelativeRectangle (newBoundsExpression)); } void Component::setBoundsRelative (const float x, const float y, const float w, const float h) { const int pw = getParentWidth(); const int ph = getParentHeight(); setBounds (roundToInt (x * pw), roundToInt (y * ph), roundToInt (w * pw), roundToInt (h * ph)); } void Component::setCentrePosition (const int x, const int y) { setTopLeftPosition (x - getWidth() / 2, y - getHeight() / 2); } void Component::setCentreRelative (const float x, const float y) { setCentrePosition (roundToInt (getParentWidth() * x), roundToInt (getParentHeight() * y)); } void Component::centreWithSize (const int width, const int height) { const Rectangle parentArea (ComponentHelpers::getParentOrMainMonitorBounds (*this)); setBounds (parentArea.getCentreX() - width / 2, parentArea.getCentreY() - height / 2, width, height); } void Component::setBoundsInset (const BorderSize& borders) { setBounds (borders.subtractedFrom (ComponentHelpers::getParentOrMainMonitorBounds (*this))); } void Component::setBoundsToFit (int x, int y, int width, int height, const Justification& justification, const bool onlyReduceInSize) { // it's no good calling this method unless both the component and // target rectangle have a finite size. jassert (getWidth() > 0 && getHeight() > 0 && width > 0 && height > 0); if (getWidth() > 0 && getHeight() > 0 && width > 0 && height > 0) { int newW, newH; if (onlyReduceInSize && getWidth() <= width && getHeight() <= height) { newW = getWidth(); newH = getHeight(); } else { const double imageRatio = getHeight() / (double) getWidth(); const double targetRatio = height / (double) width; if (imageRatio <= targetRatio) { newW = width; newH = jmin (height, roundToInt (newW * imageRatio)); } else { newH = height; newW = jmin (width, roundToInt (newH / imageRatio)); } } if (newW > 0 && newH > 0) setBounds (justification.appliedToRectangle (Rectangle (0, 0, newW, newH), Rectangle (x, y, width, height))); } } bool Component::isTransformed() const noexcept { return affineTransform != nullptr; } void Component::setTransform (const AffineTransform& newTransform) { // If you pass in a transform with no inverse, the component will have no dimensions, // and there will be all sorts of maths errors when converting coordinates. jassert (! newTransform.isSingularity()); if (newTransform.isIdentity()) { if (affineTransform != nullptr) { repaint(); affineTransform = nullptr; repaint(); sendMovedResizedMessages (false, false); } } else if (affineTransform == nullptr) { repaint(); affineTransform = new AffineTransform (newTransform); repaint(); sendMovedResizedMessages (false, false); } else if (*affineTransform != newTransform) { repaint(); *affineTransform = newTransform; repaint(); sendMovedResizedMessages (false, false); } } AffineTransform Component::getTransform() const { return affineTransform != nullptr ? *affineTransform : AffineTransform::identity; } bool Component::hitTest (int x, int y) { if (! flags.ignoresMouseClicksFlag) return true; if (flags.allowChildMouseClicksFlag) { for (int i = getNumChildComponents(); --i >= 0;) { Component& child = *getChildComponent (i); if (child.isVisible() && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y)))) return true; } } return false; } void Component::setInterceptsMouseClicks (const bool allowClicks, const bool allowClicksOnChildComponents) noexcept { flags.ignoresMouseClicksFlag = ! allowClicks; flags.allowChildMouseClicksFlag = allowClicksOnChildComponents; } void Component::getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, bool& allowsClicksOnChildComponents) const noexcept { allowsClicksOnThisComponent = ! flags.ignoresMouseClicksFlag; allowsClicksOnChildComponents = flags.allowChildMouseClicksFlag; } bool Component::contains (const Point& point) { if (ComponentHelpers::hitTest (*this, point)) { if (parentComponent != nullptr) { return parentComponent->contains (ComponentHelpers::convertToParentSpace (*this, point)); } else if (flags.hasHeavyweightPeerFlag) { const ComponentPeer* const peer = getPeer(); if (peer != nullptr) return peer->contains (point, true); } } return false; } bool Component::reallyContains (const Point& point, const bool returnTrueIfWithinAChild) { if (! contains (point)) return false; Component* const top = getTopLevelComponent(); const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, point)); return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition)); } Component* Component::getComponentAt (const Point& position) { if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) { for (int i = childComponentList.size(); --i >= 0;) { Component* child = childComponentList.getUnchecked(i); child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); if (child != nullptr) return child; } return this; } return nullptr; } Component* Component::getComponentAt (const int x, const int y) { return getComponentAt (Point (x, y)); } void Component::addChildComponent (Component* const child, int zOrder) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (child != nullptr && child->parentComponent != this) { if (child->parentComponent != nullptr) child->parentComponent->removeChildComponent (child); else child->removeFromDesktop(); child->parentComponent = this; if (child->isVisible()) child->repaintParent(); if (! child->isAlwaysOnTop()) { if (zOrder < 0 || zOrder > childComponentList.size()) zOrder = childComponentList.size(); while (zOrder > 0) { if (! childComponentList.getUnchecked (zOrder - 1)->isAlwaysOnTop()) break; --zOrder; } } childComponentList.insert (zOrder, child); child->internalHierarchyChanged(); internalChildrenChanged(); } } void Component::addAndMakeVisible (Component* const child, int zOrder) { if (child != nullptr) { child->setVisible (true); addChildComponent (child, zOrder); } } void Component::removeChildComponent (Component* const child) { removeChildComponent (childComponentList.indexOf (child), true, true); } Component* Component::removeChildComponent (const int index) { return removeChildComponent (index, true, true); } Component* Component::removeChildComponent (const int index, bool sendParentEvents, const bool sendChildEvents) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED Component* const child = childComponentList [index]; if (child != nullptr) { sendParentEvents = sendParentEvents && child->isShowing(); if (sendParentEvents) { sendFakeMouseMove(); child->repaintParent(); } childComponentList.remove (index); child->parentComponent = nullptr; // (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) { if (sendParentEvents) { const WeakReference thisPointer (this); giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); if (thisPointer == nullptr) return child; grabKeyboardFocus(); } else { giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); } } if (sendChildEvents) child->internalHierarchyChanged(); if (sendParentEvents) internalChildrenChanged(); } return child; } void Component::removeAllChildren() { while (childComponentList.size() > 0) removeChildComponent (childComponentList.size() - 1); } void Component::deleteAllChildren() { while (childComponentList.size() > 0) delete (removeChildComponent (childComponentList.size() - 1)); } int Component::getNumChildComponents() const noexcept { return childComponentList.size(); } Component* Component::getChildComponent (const int index) const noexcept { return childComponentList [index]; } int Component::getIndexOfChildComponent (const Component* const child) const noexcept { return childComponentList.indexOf (const_cast (child)); } Component* Component::getTopLevelComponent() const noexcept { const Component* comp = this; while (comp->parentComponent != nullptr) comp = comp->parentComponent; return const_cast (comp); } bool Component::isParentOf (const Component* possibleChild) const noexcept { while (possibleChild != nullptr) { possibleChild = possibleChild->parentComponent; if (possibleChild == this) return true; } return false; } void Component::parentHierarchyChanged() { } void Component::childrenChanged() { } void Component::internalChildrenChanged() { if (componentListeners.isEmpty()) { childrenChanged(); } else { BailOutChecker checker (this); childrenChanged(); if (! checker.shouldBailOut()) componentListeners.callChecked (checker, &ComponentListener::componentChildrenChanged, *this); } } void Component::internalHierarchyChanged() { BailOutChecker checker (this); parentHierarchyChanged(); if (checker.shouldBailOut()) return; componentListeners.callChecked (checker, &ComponentListener::componentParentHierarchyChanged, *this); if (checker.shouldBailOut()) return; for (int i = childComponentList.size(); --i >= 0;) { childComponentList.getUnchecked (i)->internalHierarchyChanged(); if (checker.shouldBailOut()) { // you really shouldn't delete the parent component during a callback telling you // that it's changed.. jassertfalse; return; } i = jmin (i, childComponentList.size()); } } #if JUCE_MODAL_LOOPS_PERMITTED int Component::runModalLoop() { if (! MessageManager::getInstance()->isThisTheMessageThread()) { // use a callback so this can be called from non-gui threads return (int) (pointer_sized_int) MessageManager::getInstance() ->callFunctionOnMessageThread (&ComponentHelpers::runModalLoopCallback, this); } if (! isCurrentlyModal()) enterModalState (true); return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); } #endif class ModalAutoDeleteCallback : public ModalComponentManager::Callback { public: ModalAutoDeleteCallback (Component* const comp_) : comp (comp_) {} void modalStateFinished (int) { delete comp.get(); } private: WeakReference comp; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModalAutoDeleteCallback); }; void Component::enterModalState (const bool shouldTakeKeyboardFocus, ModalComponentManager::Callback* callback, const bool deleteWhenDismissed) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED // Check for an attempt to make a component modal when it already is! // This can cause nasty problems.. jassert (! flags.currentlyModalFlag); if (! isCurrentlyModal()) { ModalComponentManager* const mcm = ModalComponentManager::getInstance(); mcm->startModal (this); mcm->attachCallback (this, callback); if (deleteWhenDismissed) mcm->attachCallback (this, new ModalAutoDeleteCallback (this)); flags.currentlyModalFlag = true; setVisible (true); if (shouldTakeKeyboardFocus) grabKeyboardFocus(); } } void Component::exitModalState (const int returnValue) { if (flags.currentlyModalFlag) { if (MessageManager::getInstance()->isThisTheMessageThread()) { ModalComponentManager::getInstance()->endModal (this, returnValue); flags.currentlyModalFlag = false; ModalComponentManager::getInstance()->bringModalComponentsToFront(); } else { class ExitModalStateMessage : public CallbackMessage { public: ExitModalStateMessage (Component* const target_, const int result_) : target (target_), result (result_) {} void messageCallback() { if (target.get() != nullptr) // (get() required for VS2003 bug) target->exitModalState (result); } private: WeakReference target; int result; }; (new ExitModalStateMessage (this, returnValue))->post(); } } } bool Component::isCurrentlyModal() const noexcept { return flags.currentlyModalFlag && getCurrentlyModalComponent() == this; } bool Component::isCurrentlyBlockedByAnotherModalComponent() const { Component* const mc = getCurrentlyModalComponent(); return ! (mc == nullptr || mc == this || mc->isParentOf (this) || mc->canModalEventBeSentToComponent (this)); } int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() noexcept { return ModalComponentManager::getInstance()->getNumModalComponents(); } Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) noexcept { return ModalComponentManager::getInstance()->getModalComponent (index); } void Component::setBroughtToFrontOnMouseClick (const bool shouldBeBroughtToFront) noexcept { flags.bringToFrontOnClickFlag = shouldBeBroughtToFront; } bool Component::isBroughtToFrontOnMouseClick() const noexcept { return flags.bringToFrontOnClickFlag; } void Component::setMouseCursor (const MouseCursor& newCursor) { if (cursor != newCursor) { cursor = newCursor; if (flags.visibleFlag) updateMouseCursor(); } } const MouseCursor Component::getMouseCursor() { return cursor; } void Component::updateMouseCursor() const { Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); } void Component::setRepaintsOnMouseActivity (const bool shouldRepaint) noexcept { flags.repaintOnMouseActivityFlag = shouldRepaint; } void Component::setAlpha (const float newAlpha) { const uint8 newIntAlpha = (uint8) (255 - jlimit (0, 255, roundToInt (newAlpha * 255.0))); if (componentTransparency != newIntAlpha) { componentTransparency = newIntAlpha; if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) peer->setAlpha (newAlpha); } else { repaint(); } } } float Component::getAlpha() const { return (255 - componentTransparency) / 255.0f; } void Component::repaintParent() { if (flags.visibleFlag) internalRepaint (0, 0, getWidth(), getHeight()); } void Component::repaint() { repaint (0, 0, getWidth(), getHeight()); } void Component::repaint (const int x, const int y, const int w, const int h) { bufferedImage = Image::null; if (flags.visibleFlag) internalRepaint (x, y, w, h); } void Component::repaint (const Rectangle& area) { repaint (area.getX(), area.getY(), area.getWidth(), area.getHeight()); } void Component::internalRepaint (int x, int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (x < 0) { w += x; x = 0; } if (x + w > getWidth()) w = getWidth() - x; if (w > 0) { if (y < 0) { h += y; y = 0; } if (y + h > getHeight()) h = getHeight() - y; if (h > 0) { if (parentComponent != nullptr) { if (parentComponent->flags.visibleFlag) { if (affineTransform == nullptr) { parentComponent->internalRepaint (x + getX(), y + getY(), w, h); } else { const Rectangle r (ComponentHelpers::convertToParentSpace (*this, Rectangle (x, y, w, h))); parentComponent->internalRepaint (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } } } else if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) peer->repaint (Rectangle (x, y, w, h)); } } } } void Component::paintComponent (Graphics& g) { if (flags.bufferToImageFlag) { if (bufferedImage.isNull()) { bufferedImage = Image (flags.opaqueFlag ? Image::RGB : Image::ARGB, getWidth(), getHeight(), ! flags.opaqueFlag, Image::NativeImage); Graphics imG (bufferedImage); paint (imG); } g.setColour (Colours::black); g.drawImageAt (bufferedImage, 0, 0); } else { paint (g); } } void Component::paintWithinParentContext (Graphics& g) { g.setOrigin (getX(), getY()); paintEntireComponent (g, false); } void Component::paintComponentAndChildren (Graphics& g) { const Rectangle clipBounds (g.getClipBounds()); if (flags.dontClipGraphicsFlag) { paintComponent (g); } else { g.saveState(); ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point()); if (! g.isClipEmpty()) paintComponent (g); g.restoreState(); } for (int i = 0; i < childComponentList.size(); ++i) { Component& child = *childComponentList.getUnchecked (i); if (child.isVisible()) { if (child.affineTransform != nullptr) { g.saveState(); g.addTransform (*child.affineTransform); if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds())) child.paintWithinParentContext (g); g.restoreState(); } else if (clipBounds.intersects (child.getBounds())) { g.saveState(); if (child.flags.dontClipGraphicsFlag) { child.paintWithinParentContext (g); } else if (g.reduceClipRegion (child.getBounds())) { bool nothingClipped = true; for (int j = i + 1; j < childComponentList.size(); ++j) { const Component& sibling = *childComponentList.getUnchecked (j); if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr) { nothingClipped = false; g.excludeClipRegion (sibling.getBounds()); } } if (nothingClipped || ! g.isClipEmpty()) child.paintWithinParentContext (g); } g.restoreState(); } } } g.saveState(); paintOverChildren (g); g.restoreState(); } void Component::paintEntireComponent (Graphics& g, const bool ignoreAlphaLevel) { jassert (! g.isClipEmpty()); #if JUCE_DEBUG flags.isInsidePaintCall = true; #endif if (effect != nullptr) { Image effectImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, getWidth(), getHeight(), ! flags.opaqueFlag, Image::NativeImage); { Graphics g2 (effectImage); paintComponentAndChildren (g2); } effect->applyEffect (effectImage, g, ignoreAlphaLevel ? 1.0f : getAlpha()); } else if (componentTransparency > 0 && ! ignoreAlphaLevel) { if (componentTransparency < 255) { g.beginTransparencyLayer (getAlpha()); paintComponentAndChildren (g); g.endTransparencyLayer(); } } else { paintComponentAndChildren (g); } #if JUCE_DEBUG flags.isInsidePaintCall = false; #endif } void Component::setPaintingIsUnclipped (const bool shouldPaintWithoutClipping) noexcept { flags.dontClipGraphicsFlag = shouldPaintWithoutClipping; } Image Component::createComponentSnapshot (const Rectangle& areaToGrab, const bool clipImageToComponentBounds) { Rectangle r (areaToGrab); if (clipImageToComponentBounds) r = r.getIntersection (getLocalBounds()); Image componentImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, jmax (1, r.getWidth()), jmax (1, r.getHeight()), true); Graphics imageContext (componentImage); imageContext.setOrigin (-r.getX(), -r.getY()); paintEntireComponent (imageContext, true); return componentImage; } void Component::setComponentEffect (ImageEffectFilter* const newEffect) { if (effect != newEffect) { effect = newEffect; repaint(); } } LookAndFeel& Component::getLookAndFeel() const noexcept { const Component* c = this; do { if (c->lookAndFeel != nullptr) return *(c->lookAndFeel); c = c->parentComponent; } while (c != nullptr); return LookAndFeel::getDefaultLookAndFeel(); } void Component::setLookAndFeel (LookAndFeel* const newLookAndFeel) { if (lookAndFeel != newLookAndFeel) { lookAndFeel = newLookAndFeel; sendLookAndFeelChange(); } } void Component::lookAndFeelChanged() { } void Component::sendLookAndFeelChange() { repaint(); WeakReference safePointer (this); lookAndFeelChanged(); if (safePointer != nullptr) { for (int i = childComponentList.size(); --i >= 0;) { childComponentList.getUnchecked (i)->sendLookAndFeelChange(); if (safePointer == nullptr) return; i = jmin (i, childComponentList.size()); } } } const Colour Component::findColour (const int colourId, const bool inheritFromParent) const { const var* const v = properties.getVarPointer (ComponentHelpers::getColourPropertyId (colourId)); if (v != nullptr) return Colour ((int) *v); if (inheritFromParent && parentComponent != nullptr && (lookAndFeel == nullptr || ! lookAndFeel->isColourSpecified (colourId))) return parentComponent->findColour (colourId, true); return getLookAndFeel().findColour (colourId); } bool Component::isColourSpecified (const int colourId) const { return properties.contains (ComponentHelpers::getColourPropertyId (colourId)); } void Component::removeColour (const int colourId) { if (properties.remove (ComponentHelpers::getColourPropertyId (colourId))) colourChanged(); } void Component::setColour (const int colourId, const Colour& colour) { if (properties.set (ComponentHelpers::getColourPropertyId (colourId), (int) colour.getARGB())) colourChanged(); } void Component::copyAllExplicitColoursTo (Component& target) const { bool changed = false; for (int i = properties.size(); --i >= 0;) { const Identifier name (properties.getName(i)); if (name.toString().startsWith ("jcclr_")) if (target.properties.set (name, properties [name])) changed = true; } if (changed) target.colourChanged(); } void Component::colourChanged() { } MarkerList* Component::getMarkers (bool /*xAxis*/) { return nullptr; } Component::Positioner::Positioner (Component& component_) noexcept : component (component_) { } Component::Positioner* Component::getPositioner() const noexcept { return positioner; } void Component::setPositioner (Positioner* newPositioner) { // You can only assign a positioner to the component that it was created for! jassert (newPositioner == nullptr || this == &(newPositioner->getComponent())); positioner = newPositioner; } Rectangle Component::getLocalBounds() const noexcept { return Rectangle (getWidth(), getHeight()); } Rectangle Component::getBoundsInParent() const noexcept { return affineTransform == nullptr ? bounds : bounds.toFloat().transformed (*affineTransform).getSmallestIntegerContainer(); } void Component::getVisibleArea (RectangleList& result, const bool includeSiblings) const { result.clear(); const Rectangle unclipped (ComponentHelpers::getUnclippedArea (*this)); if (! unclipped.isEmpty()) { result.add (unclipped); if (includeSiblings) { const Component* const c = getTopLevelComponent(); ComponentHelpers::subtractObscuredRegions (*c, result, getLocalPoint (c, Point()), c->getLocalBounds(), this); } ComponentHelpers::subtractObscuredRegions (*this, result, Point(), unclipped, nullptr); result.consolidate(); } } void Component::mouseEnter (const MouseEvent&) { // base class does nothing } void Component::mouseExit (const MouseEvent&) { // base class does nothing } void Component::mouseDown (const MouseEvent&) { // base class does nothing } void Component::mouseUp (const MouseEvent&) { // base class does nothing } void Component::mouseDrag (const MouseEvent&) { // base class does nothing } void Component::mouseMove (const MouseEvent&) { // base class does nothing } void Component::mouseDoubleClick (const MouseEvent&) { // base class does nothing } void Component::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { // the base class just passes this event up to its parent.. if (parentComponent != nullptr) parentComponent->mouseWheelMove (e.getEventRelativeTo (parentComponent), wheelIncrementX, wheelIncrementY); } void Component::resized() { // base class does nothing } void Component::moved() { // base class does nothing } void Component::childBoundsChanged (Component*) { // base class does nothing } void Component::parentSizeChanged() { // base class does nothing } void Component::addComponentListener (ComponentListener* const newListener) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED componentListeners.add (newListener); } void Component::removeComponentListener (ComponentListener* const listenerToRemove) { componentListeners.remove (listenerToRemove); } void Component::inputAttemptWhenModal() { ModalComponentManager::getInstance()->bringModalComponentsToFront(); getLookAndFeel().playAlertSound(); } bool Component::canModalEventBeSentToComponent (const Component*) { return false; } void Component::internalModalInputAttempt() { Component* const current = getCurrentlyModalComponent(); if (current != nullptr) current->inputAttemptWhenModal(); } void Component::paint (Graphics&) { // all painting is done in the subclasses jassert (! isOpaque()); // if your component's opaque, you've gotta paint it! } void Component::paintOverChildren (Graphics&) { // all painting is done in the subclasses } void Component::postCommandMessage (const int commandId) { class CustomCommandMessage : public CallbackMessage { public: CustomCommandMessage (Component* const target_, const int commandId_) : target (target_), commandId (commandId_) {} void messageCallback() { if (target.get() != nullptr) // (get() required for VS2003 bug) target->handleCommandMessage (commandId); } private: WeakReference target; int commandId; }; (new CustomCommandMessage (this, commandId))->post(); } void Component::handleCommandMessage (int) { // used by subclasses } void Component::addMouseListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED // If you register a component as a mouselistener for itself, it'll receive all the events // twice - once via the direct callback that all components get anyway, and then again as a listener! jassert ((newListener != this) || wantsEventsForAllNestedChildComponents); if (mouseListeners == nullptr) mouseListeners = new MouseListenerList(); mouseListeners->addListener (newListener, wantsEventsForAllNestedChildComponents); } void Component::removeMouseListener (MouseListener* const listenerToRemove) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (mouseListeners != nullptr) mouseListeners->removeListener (listenerToRemove); } void Component::internalMouseEnter (MouseInputSource& source, const Point& relativePos, const Time& time) { if (isCurrentlyBlockedByAnotherModalComponent()) { // if something else is modal, always just show a normal mouse cursor source.showMouseCursor (MouseCursor::NormalCursor); return; } if (! flags.mouseInsideFlag) { flags.mouseInsideFlag = true; flags.mouseOverFlag = true; flags.mouseDownFlag = false; BailOutChecker checker (this); if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); mouseEnter (me); if (checker.shouldBailOut()) return; Desktop& desktop = Desktop::getInstance(); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseEnter, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseEnter, me); } } void Component::internalMouseExit (MouseInputSource& source, const Point& relativePos, const Time& time) { BailOutChecker checker (this); if (flags.mouseDownFlag) { internalMouseUp (source, relativePos, time, source.getCurrentModifiers().getRawFlags()); if (checker.shouldBailOut()) return; } if (flags.mouseInsideFlag || flags.mouseOverFlag) { flags.mouseInsideFlag = false; flags.mouseOverFlag = false; flags.mouseDownFlag = false; if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); mouseExit (me); if (checker.shouldBailOut()) return; Desktop& desktop = Desktop::getInstance(); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseExit, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); } } void Component::internalMouseDown (MouseInputSource& source, const Point& relativePos, const Time& time) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); if (isCurrentlyBlockedByAnotherModalComponent()) { internalModalInputAttempt(); if (checker.shouldBailOut()) return; // If processing the input attempt has exited the modal loop, we'll allow the event // to be delivered.. if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDown, me); return; } } { Component* c = this; while (c != nullptr) { if (c->isBroughtToFrontOnMouseClick()) { c->toFront (true); if (checker.shouldBailOut()) return; } c = c->parentComponent; } } if (! flags.dontFocusOnMouseClickFlag) { grabFocusInternal (focusChangedByMouseClick); if (checker.shouldBailOut()) return; } flags.mouseDownFlag = true; flags.mouseOverFlag = true; if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); mouseDown (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDown, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDown, me); } void Component::internalMouseUp (MouseInputSource& source, const Point& relativePos, const Time& time, const ModifierKeys& oldModifiers) { if (flags.mouseDownFlag) { flags.mouseDownFlag = false; BailOutChecker checker (this); if (flags.repaintOnMouseActivityFlag) repaint(); const MouseEvent me (source, relativePos, oldModifiers, this, this, time, getLocalPoint (nullptr, source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), source.hasMouseMovedSignificantlySincePressed()); mouseUp (me); if (checker.shouldBailOut()) return; Desktop& desktop = Desktop::getInstance(); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseUp, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseUp, me); if (checker.shouldBailOut()) return; // check for double-click if (me.getNumberOfClicks() >= 2) { mouseDoubleClick (me); if (checker.shouldBailOut()) return; desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDoubleClick, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDoubleClick, me); } } } void Component::internalMouseDrag (MouseInputSource& source, const Point& relativePos, const Time& time) { if (flags.mouseDownFlag) { flags.mouseOverFlag = reallyContains (relativePos, false); BailOutChecker checker (this); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, getLocalPoint (nullptr, source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), source.hasMouseMovedSignificantlySincePressed()); mouseDrag (me); if (checker.shouldBailOut()) return; Desktop& desktop = Desktop::getInstance(); desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDrag, me); } } void Component::internalMouseMove (MouseInputSource& source, const Point& relativePos, const Time& time) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. desktop.sendMouseMove(); } else { flags.mouseOverFlag = true; mouseMove (me); if (checker.shouldBailOut()) return; desktop.resetTimer(); desktop.mouseListeners.callChecked (checker, &MouseListener::mouseMove, me); MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseMove, me); } } void Component::internalMouseWheel (MouseInputSource& source, const Point& relativePos, const Time& time, const float amountX, const float amountY) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); const float wheelIncrementX = amountX / 256.0f; const float wheelIncrementY = amountY / 256.0f; const MouseEvent me (source, relativePos, source.getCurrentModifiers(), this, this, time, relativePos, time, 0, false); if (isCurrentlyBlockedByAnotherModalComponent()) { // allow blocked mouse-events to go to global listeners.. desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheelIncrementX, wheelIncrementY); } else { mouseWheelMove (me, wheelIncrementX, wheelIncrementY); if (checker.shouldBailOut()) return; desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheelIncrementX, wheelIncrementY); if (! checker.shouldBailOut()) MouseListenerList::sendWheelEvent (*this, checker, me, wheelIncrementX, wheelIncrementY); } } void Component::sendFakeMouseMove() const { MouseInputSource& mainMouse = Desktop::getInstance().getMainMouseSource(); if (! mainMouse.isDragging()) mainMouse.triggerFakeMove(); } void Component::beginDragAutoRepeat (const int interval) { Desktop::getInstance().beginDragAutoRepeat (interval); } void Component::broughtToFront() { } void Component::internalBroughtToFront() { if (flags.hasHeavyweightPeerFlag) Desktop::getInstance().componentBroughtToFront (this); BailOutChecker checker (this); broughtToFront(); if (checker.shouldBailOut()) return; componentListeners.callChecked (checker, &ComponentListener::componentBroughtToFront, *this); if (checker.shouldBailOut()) return; // When brought to the front and there's a modal component blocking this one, // we need to bring the modal one to the front instead.. Component* const cm = getCurrentlyModalComponent(); if (cm != nullptr && cm->getTopLevelComponent() != getTopLevelComponent()) ModalComponentManager::getInstance()->bringModalComponentsToFront (false); // very important that this is false, otherwise in win32, // non-front components can't get focus when another modal comp is // active, and therefore can't receive mouse-clicks } void Component::focusGained (FocusChangeType) { // base class does nothing } void Component::internalFocusGain (const FocusChangeType cause) { internalFocusGain (cause, WeakReference (this)); } void Component::internalFocusGain (const FocusChangeType cause, const WeakReference& safePointer) { focusGained (cause); if (safePointer != nullptr) internalChildFocusChange (cause, safePointer); } void Component::focusLost (FocusChangeType) { // base class does nothing } void Component::internalFocusLoss (const FocusChangeType cause) { WeakReference safePointer (this); focusLost (focusChangedDirectly); if (safePointer != nullptr) internalChildFocusChange (cause, safePointer); } void Component::focusOfChildComponentChanged (FocusChangeType /*cause*/) { // base class does nothing } void Component::internalChildFocusChange (FocusChangeType cause, const WeakReference& safePointer) { const bool childIsNowFocused = hasKeyboardFocus (true); if (flags.childCompFocusedFlag != childIsNowFocused) { flags.childCompFocusedFlag = childIsNowFocused; focusOfChildComponentChanged (cause); if (safePointer == nullptr) return; } if (parentComponent != nullptr) parentComponent->internalChildFocusChange (cause, WeakReference (parentComponent)); } bool Component::isEnabled() const noexcept { return (! flags.isDisabledFlag) && (parentComponent == nullptr || parentComponent->isEnabled()); } void Component::setEnabled (const bool shouldBeEnabled) { if (flags.isDisabledFlag == shouldBeEnabled) { flags.isDisabledFlag = ! shouldBeEnabled; // if any parent components are disabled, setting our flag won't make a difference, // so no need to send a change message if (parentComponent == nullptr || parentComponent->isEnabled()) sendEnablementChangeMessage(); } } void Component::sendEnablementChangeMessage() { WeakReference safePointer (this); enablementChanged(); if (safePointer == nullptr) return; for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if (c != nullptr) { c->sendEnablementChangeMessage(); if (safePointer == nullptr) return; } } } void Component::enablementChanged() { } void Component::setWantsKeyboardFocus (const bool wantsFocus) noexcept { flags.wantsFocusFlag = wantsFocus; } void Component::setMouseClickGrabsKeyboardFocus (const bool shouldGrabFocus) { flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus; } bool Component::getMouseClickGrabsKeyboardFocus() const noexcept { return ! flags.dontFocusOnMouseClickFlag; } bool Component::getWantsKeyboardFocus() const noexcept { return flags.wantsFocusFlag && ! flags.isDisabledFlag; } void Component::setFocusContainer (const bool shouldBeFocusContainer) noexcept { flags.isFocusContainerFlag = shouldBeFocusContainer; } bool Component::isFocusContainer() const noexcept { return flags.isFocusContainerFlag; } static const Identifier juce_explicitFocusOrderId ("_jexfo"); int Component::getExplicitFocusOrder() const { return properties [juce_explicitFocusOrderId]; } void Component::setExplicitFocusOrder (const int newFocusOrderIndex) { properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); } KeyboardFocusTraverser* Component::createFocusTraverser() { if (flags.isFocusContainerFlag || parentComponent == nullptr) return new KeyboardFocusTraverser(); return parentComponent->createFocusTraverser(); } void Component::takeKeyboardFocus (const FocusChangeType cause) { // give the focus to this component if (currentlyFocusedComponent != this) { // get the focus onto our desktop window ComponentPeer* const peer = getPeer(); if (peer != nullptr) { WeakReference safePointer (this); peer->grabFocus(); if (peer->isFocused() && currentlyFocusedComponent != this) { WeakReference componentLosingFocus (currentlyFocusedComponent); currentlyFocusedComponent = this; Desktop::getInstance().triggerFocusCallback(); // call this after setting currentlyFocusedComponent so that the one that's // losing it has a chance to see where focus is going if (componentLosingFocus != nullptr) componentLosingFocus->internalFocusLoss (cause); if (currentlyFocusedComponent == this) internalFocusGain (cause, safePointer); } } } } void Component::grabFocusInternal (const FocusChangeType cause, const bool canTryParent) { if (isShowing()) { if (flags.wantsFocusFlag && (isEnabled() || parentComponent == nullptr)) { takeKeyboardFocus (cause); } else { if (isParentOf (currentlyFocusedComponent) && currentlyFocusedComponent->isShowing()) { // do nothing if the focused component is actually a child of ours.. } else { // find the default child component.. ScopedPointer traverser (createFocusTraverser()); if (traverser != nullptr) { Component* const defaultComp = traverser->getDefaultComponent (this); traverser = nullptr; if (defaultComp != nullptr) { defaultComp->grabFocusInternal (cause, false); return; } } if (canTryParent && parentComponent != nullptr) { // if no children want it and we're allowed to try our parent comp, // then pass up to parent, which will try our siblings. parentComponent->grabFocusInternal (cause, true); } } } } } void Component::grabKeyboardFocus() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED grabFocusInternal (focusChangedDirectly); } void Component::moveKeyboardFocusToSibling (const bool moveToNext) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED if (parentComponent != nullptr) { ScopedPointer traverser (createFocusTraverser()); if (traverser != nullptr) { Component* const nextComp = moveToNext ? traverser->getNextComponent (this) : traverser->getPreviousComponent (this); traverser = nullptr; if (nextComp != nullptr) { if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) { WeakReference nextCompPointer (nextComp); internalModalInputAttempt(); if (nextCompPointer == nullptr || nextComp->isCurrentlyBlockedByAnotherModalComponent()) return; } nextComp->grabFocusInternal (focusChangedByTabKey); return; } } parentComponent->moveKeyboardFocusToSibling (moveToNext); } } bool Component::hasKeyboardFocus (const bool trueIfChildIsFocused) const { return (currentlyFocusedComponent == this) || (trueIfChildIsFocused && isParentOf (currentlyFocusedComponent)); } Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() noexcept { return currentlyFocusedComponent; } void Component::giveAwayFocus (const bool sendFocusLossEvent) { Component* const componentLosingFocus = currentlyFocusedComponent; currentlyFocusedComponent = nullptr; if (sendFocusLossEvent && componentLosingFocus != nullptr) componentLosingFocus->internalFocusLoss (focusChangedDirectly); Desktop::getInstance().triggerFocusCallback(); } bool Component::isMouseOver (const bool includeChildren) const { if (flags.mouseOverFlag) return true; if (includeChildren) { Desktop& desktop = Desktop::getInstance(); for (int i = desktop.getNumMouseSources(); --i >= 0;) { Component* const c = desktop.getMouseSource(i)->getComponentUnderMouse(); if (isParentOf (c) && c->flags.mouseOverFlag) // (mouseOverFlag checked in case it's being dragged outside the comp) return true; } } return false; } bool Component::isMouseButtonDown() const noexcept { return flags.mouseDownFlag; } bool Component::isMouseOverOrDragging() const noexcept { return flags.mouseOverFlag || flags.mouseDownFlag; } bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() noexcept { return ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(); } Point Component::getMouseXYRelative() const { return getLocalPoint (nullptr, Desktop::getMousePosition()); } Rectangle Component::getParentMonitorArea() const { return Desktop::getInstance().getMonitorAreaContaining (getScreenBounds().getCentre()); } void Component::addKeyListener (KeyListener* const newListener) { if (keyListeners == nullptr) keyListeners = new Array (); keyListeners->addIfNotAlreadyThere (newListener); } void Component::removeKeyListener (KeyListener* const listenerToRemove) { if (keyListeners != nullptr) keyListeners->removeValue (listenerToRemove); } bool Component::keyPressed (const KeyPress&) { return false; } bool Component::keyStateChanged (const bool /*isKeyDown*/) { return false; } void Component::modifierKeysChanged (const ModifierKeys& modifiers) { if (parentComponent != nullptr) parentComponent->modifierKeysChanged (modifiers); } void Component::internalModifierKeysChanged() { sendFakeMouseMove(); modifierKeysChanged (ModifierKeys::getCurrentModifiers()); } ComponentPeer* Component::getPeer() const { if (flags.hasHeavyweightPeerFlag) return ComponentPeer::getPeerFor (this); else if (parentComponent == nullptr) return nullptr; return parentComponent->getPeer(); } Component::BailOutChecker::BailOutChecker (Component* const component) : safePointer (component) { jassert (component != nullptr); } bool Component::BailOutChecker::shouldBailOut() const noexcept { return safePointer == nullptr; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Component.cpp ***/ /*** Start of inlined file: juce_ComponentListener.cpp ***/ BEGIN_JUCE_NAMESPACE void ComponentListener::componentMovedOrResized (Component&, bool, bool) {} void ComponentListener::componentBroughtToFront (Component&) {} void ComponentListener::componentVisibilityChanged (Component&) {} void ComponentListener::componentChildrenChanged (Component&) {} void ComponentListener::componentParentHierarchyChanged (Component&) {} void ComponentListener::componentNameChanged (Component&) {} void ComponentListener::componentBeingDeleted (Component&) {} END_JUCE_NAMESPACE /*** End of inlined file: juce_ComponentListener.cpp ***/ /*** Start of inlined file: juce_Desktop.cpp ***/ BEGIN_JUCE_NAMESPACE Desktop::Desktop() : mouseClickCounter (0), kioskModeComponent (nullptr), allowedOrientations (allOrientations) { createMouseInputSources(); refreshMonitorSizes(); } Desktop::~Desktop() { jassert (instance == this); instance = nullptr; // doh! If you don't delete all your windows before exiting, you're going to // be leaking memory! jassert (desktopComponents.size() == 0); } Desktop& JUCE_CALLTYPE Desktop::getInstance() { if (instance == nullptr) instance = new Desktop(); return *instance; } Desktop* Desktop::instance = nullptr; void Desktop::refreshMonitorSizes() { Array > oldClipped, oldUnclipped; oldClipped.swapWithArray (monitorCoordsClipped); oldUnclipped.swapWithArray (monitorCoordsUnclipped); getCurrentMonitorPositions (monitorCoordsClipped, true); getCurrentMonitorPositions (monitorCoordsUnclipped, false); jassert (monitorCoordsClipped.size() == monitorCoordsUnclipped.size()); if (oldClipped != monitorCoordsClipped || oldUnclipped != monitorCoordsUnclipped) { for (int i = ComponentPeer::getNumPeers(); --i >= 0;) { ComponentPeer* const p = ComponentPeer::getPeer (i); if (p != nullptr) p->handleScreenSizeChange(); } } } int Desktop::getNumDisplayMonitors() const noexcept { return monitorCoordsClipped.size(); } const Rectangle Desktop::getDisplayMonitorCoordinates (const int index, const bool clippedToWorkArea) const noexcept { return clippedToWorkArea ? monitorCoordsClipped [index] : monitorCoordsUnclipped [index]; } const RectangleList Desktop::getAllMonitorDisplayAreas (const bool clippedToWorkArea) const { RectangleList rl; for (int i = 0; i < getNumDisplayMonitors(); ++i) rl.addWithoutMerging (getDisplayMonitorCoordinates (i, clippedToWorkArea)); return rl; } const Rectangle Desktop::getMainMonitorArea (const bool clippedToWorkArea) const noexcept { return getDisplayMonitorCoordinates (0, clippedToWorkArea); } const Rectangle Desktop::getMonitorAreaContaining (const Point& position, const bool clippedToWorkArea) const { Rectangle best (getMainMonitorArea (clippedToWorkArea)); double bestDistance = 1.0e10; for (int i = getNumDisplayMonitors(); --i >= 0;) { const Rectangle rect (getDisplayMonitorCoordinates (i, clippedToWorkArea)); if (rect.contains (position)) return rect; const double distance = rect.getCentre().getDistanceFrom (position); if (distance < bestDistance) { bestDistance = distance; best = rect; } } return best; } int Desktop::getNumComponents() const noexcept { return desktopComponents.size(); } Component* Desktop::getComponent (const int index) const noexcept { return desktopComponents [index]; } Component* Desktop::findComponentAt (const Point& screenPosition) const { for (int i = desktopComponents.size(); --i >= 0;) { Component* const c = desktopComponents.getUnchecked(i); if (c->isVisible()) { const Point relative (c->getLocalPoint (nullptr, screenPosition)); if (c->contains (relative)) return c->getComponentAt (relative); } } return nullptr; } LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept { if (currentLookAndFeel == nullptr) { if (defaultLookAndFeel == nullptr) defaultLookAndFeel = new LookAndFeel(); currentLookAndFeel = defaultLookAndFeel; } return *currentLookAndFeel; } void Desktop::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel) { currentLookAndFeel = newDefaultLookAndFeel; for (int i = getNumComponents(); --i >= 0;) { Component* const c = getComponent (i); if (c != nullptr) c->sendLookAndFeelChange(); } } void Desktop::addDesktopComponent (Component* const c) { jassert (c != nullptr); jassert (! desktopComponents.contains (c)); desktopComponents.addIfNotAlreadyThere (c); } void Desktop::removeDesktopComponent (Component* const c) { desktopComponents.removeValue (c); } void Desktop::componentBroughtToFront (Component* const c) { const int index = desktopComponents.indexOf (c); jassert (index >= 0); if (index >= 0) { int newIndex = -1; if (! c->isAlwaysOnTop()) { newIndex = desktopComponents.size(); while (newIndex > 0 && desktopComponents.getUnchecked (newIndex - 1)->isAlwaysOnTop()) --newIndex; --newIndex; } desktopComponents.move (index, newIndex); } } const Point Desktop::getMousePosition() { return getInstance().getMainMouseSource().getScreenPosition(); } const Point Desktop::getLastMouseDownPosition() { return getInstance().getMainMouseSource().getLastMouseDownPosition(); } int Desktop::getMouseButtonClickCounter() { return getInstance().mouseClickCounter; } void Desktop::incrementMouseClickCounter() noexcept { ++mouseClickCounter; } int Desktop::getNumDraggingMouseSources() const noexcept { int num = 0; for (int i = mouseSources.size(); --i >= 0;) if (mouseSources.getUnchecked(i)->isDragging()) ++num; return num; } MouseInputSource* Desktop::getDraggingMouseSource (int index) const noexcept { int num = 0; for (int i = mouseSources.size(); --i >= 0;) { MouseInputSource* const mi = mouseSources.getUnchecked(i); if (mi->isDragging()) { if (index == num) return mi; ++num; } } return nullptr; } class MouseDragAutoRepeater : public Timer { public: MouseDragAutoRepeater() {} void timerCallback() { Desktop& desktop = Desktop::getInstance(); int numMiceDown = 0; for (int i = desktop.getNumMouseSources(); --i >= 0;) { MouseInputSource* const source = desktop.getMouseSource(i); if (source->isDragging()) { source->triggerFakeMove(); ++numMiceDown; } } if (numMiceDown == 0) desktop.beginDragAutoRepeat (0); } private: JUCE_DECLARE_NON_COPYABLE (MouseDragAutoRepeater); }; void Desktop::beginDragAutoRepeat (const int interval) { if (interval > 0) { if (dragRepeater == nullptr) dragRepeater = new MouseDragAutoRepeater(); if (dragRepeater->getTimerInterval() != interval) dragRepeater->startTimer (interval); } else { dragRepeater = nullptr; } } void Desktop::addFocusChangeListener (FocusChangeListener* const listener) { focusListeners.add (listener); } void Desktop::removeFocusChangeListener (FocusChangeListener* const listener) { focusListeners.remove (listener); } void Desktop::triggerFocusCallback() { triggerAsyncUpdate(); } void Desktop::handleAsyncUpdate() { // The component may be deleted during this operation, but we'll use a SafePointer rather than a // BailOutChecker so that any remaining listeners will still get a callback (with a null pointer). WeakReference currentFocus (Component::getCurrentlyFocusedComponent()); focusListeners.call (&FocusChangeListener::globalFocusChanged, currentFocus); } void Desktop::addGlobalMouseListener (MouseListener* const listener) { mouseListeners.add (listener); resetTimer(); } void Desktop::removeGlobalMouseListener (MouseListener* const listener) { mouseListeners.remove (listener); resetTimer(); } void Desktop::timerCallback() { if (lastFakeMouseMove != getMousePosition()) sendMouseMove(); } void Desktop::sendMouseMove() { if (! mouseListeners.isEmpty()) { startTimer (20); lastFakeMouseMove = getMousePosition(); Component* const target = findComponentAt (lastFakeMouseMove); if (target != nullptr) { Component::BailOutChecker checker (target); const Point pos (target->getLocalPoint (nullptr, lastFakeMouseMove)); const Time now (Time::getCurrentTime()); const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), target, target, now, pos, now, 0, false); if (me.mods.isAnyMouseButtonDown()) mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); else mouseListeners.callChecked (checker, &MouseListener::mouseMove, me); } } } void Desktop::resetTimer() { if (mouseListeners.size() == 0) stopTimer(); else startTimer (100); lastFakeMouseMove = getMousePosition(); } void Desktop::setKioskModeComponent (Component* componentToUse, const bool allowMenusAndBars) { if (kioskModeComponent != componentToUse) { // agh! Don't delete or remove a component from the desktop while it's still the kiosk component! jassert (kioskModeComponent == nullptr || ComponentPeer::getPeerFor (kioskModeComponent) != nullptr); if (kioskModeComponent != nullptr) { setKioskComponent (kioskModeComponent, false, allowMenusAndBars); kioskModeComponent->setBounds (kioskComponentOriginalBounds); } kioskModeComponent = componentToUse; if (kioskModeComponent != nullptr) { // Only components that are already on the desktop can be put into kiosk mode! jassert (ComponentPeer::getPeerFor (kioskModeComponent) != nullptr); kioskComponentOriginalBounds = kioskModeComponent->getBounds(); setKioskComponent (kioskModeComponent, true, allowMenusAndBars); } } } void Desktop::setOrientationsEnabled (const int newOrientations) { // Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation. jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0); allowedOrientations = newOrientations; } bool Desktop::isOrientationEnabled (const DisplayOrientation orientation) const noexcept { // Make sure you only pass one valid flag in here... jassert (orientation == upright || orientation == upsideDown || orientation == rotatedClockwise || orientation == rotatedAntiClockwise); return (allowedOrientations & orientation) != 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Desktop.cpp ***/ /*** Start of inlined file: juce_ModalComponentManager.cpp ***/ BEGIN_JUCE_NAMESPACE class ModalComponentManager::ModalItem : public ComponentMovementWatcher { public: ModalItem (Component* const comp) : ComponentMovementWatcher (comp), component (comp), returnValue (0), isActive (true) { jassert (comp != nullptr); } void componentMovedOrResized (bool, bool) {} void componentPeerChanged() { if (! component->isShowing()) cancel(); } void componentVisibilityChanged() { if (! component->isShowing()) cancel(); } void componentBeingDeleted (Component& comp) { ComponentMovementWatcher::componentBeingDeleted (comp); if (component == &comp || comp.isParentOf (component)) cancel(); } void cancel() { if (isActive) { isActive = false; ModalComponentManager::getInstance()->triggerAsyncUpdate(); } } Component* component; OwnedArray callbacks; int returnValue; bool isActive; private: JUCE_DECLARE_NON_COPYABLE (ModalItem); }; ModalComponentManager::ModalComponentManager() { } ModalComponentManager::~ModalComponentManager() { clearSingletonInstance(); } juce_ImplementSingleton_SingleThreaded (ModalComponentManager); void ModalComponentManager::startModal (Component* component) { if (component != nullptr) stack.add (new ModalItem (component)); } void ModalComponentManager::attachCallback (Component* component, Callback* callback) { if (callback != nullptr) { ScopedPointer callbackDeleter (callback); for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) { item->callbacks.add (callback); callbackDeleter.release(); break; } } } } void ModalComponentManager::endModal (Component* component) { for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) item->cancel(); } } void ModalComponentManager::endModal (Component* component, int returnValue) { for (int i = stack.size(); --i >= 0;) { ModalItem* const item = stack.getUnchecked(i); if (item->component == component) { item->returnValue = returnValue; item->cancel(); } } } int ModalComponentManager::getNumModalComponents() const { int n = 0; for (int i = 0; i < stack.size(); ++i) if (stack.getUnchecked(i)->isActive) ++n; return n; } Component* ModalComponentManager::getModalComponent (const int index) const { int n = 0; for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (item->isActive) if (n++ == index) return item->component; } return nullptr; } bool ModalComponentManager::isModal (Component* const comp) const { for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (item->isActive && item->component == comp) return true; } return false; } bool ModalComponentManager::isFrontModalComponent (Component* const comp) const { return comp == getModalComponent (0); } void ModalComponentManager::handleAsyncUpdate() { for (int i = stack.size(); --i >= 0;) { const ModalItem* const item = stack.getUnchecked(i); if (! item->isActive) { ScopedPointer deleter (stack.removeAndReturn (i)); for (int j = item->callbacks.size(); --j >= 0;) item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); } } } void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFocus) { ComponentPeer* lastOne = nullptr; for (int i = 0; i < getNumModalComponents(); ++i) { Component* const c = getModalComponent (i); if (c == nullptr) break; ComponentPeer* peer = c->getPeer(); if (peer != nullptr && peer != lastOne) { if (lastOne == nullptr) { peer->toFront (topOneShouldGrabFocus); if (topOneShouldGrabFocus) peer->grabFocus(); } else peer->toBehind (lastOne); lastOne = peer; } } } #if JUCE_MODAL_LOOPS_PERMITTED class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback { public: ReturnValueRetriever (int& value_, bool& finished_) : value (value_), finished (finished_) {} void modalStateFinished (int returnValue) { finished = true; value = returnValue; } private: int& value; bool& finished; JUCE_DECLARE_NON_COPYABLE (ReturnValueRetriever); }; int ModalComponentManager::runEventLoopForCurrentComponent() { // This can only be run from the message thread! jassert (MessageManager::getInstance()->isThisTheMessageThread()); Component* currentlyModal = getModalComponent (0); if (currentlyModal == nullptr) return 0; WeakReference prevFocused (Component::getCurrentlyFocusedComponent()); int returnValue = 0; bool finished = false; attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished)); JUCE_TRY { while (! finished) { if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) break; } } JUCE_CATCH_EXCEPTION if (prevFocused != nullptr) prevFocused->grabKeyboardFocus(); return returnValue; } #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_ModalComponentManager.cpp ***/ /*** Start of inlined file: juce_ArrowButton.cpp ***/ BEGIN_JUCE_NAMESPACE ArrowButton::ArrowButton (const String& name, float arrowDirectionInRadians, const Colour& arrowColour) : Button (name), colour (arrowColour) { path.lineTo (0.0f, 1.0f); path.lineTo (1.0f, 0.5f); path.closeSubPath(); path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * arrowDirectionInRadians, 0.5f, 0.5f)); setComponentEffect (&shadow); updateShadowAndOffset(); } ArrowButton::~ArrowButton() { } void ArrowButton::paintButton (Graphics& g, bool /*isMouseOverButton*/, bool /*isButtonDown*/) { g.setColour (colour); g.fillPath (path, path.getTransformToScaleToFit ((float) offset, (float) offset, (float) (getWidth() - 3), (float) (getHeight() - 3), false)); } void ArrowButton::buttonStateChanged() { updateShadowAndOffset(); } void ArrowButton::updateShadowAndOffset() { offset = (isDown()) ? 1 : 0; shadow.setShadowProperties ((isDown()) ? 1.2f : 3.0f, 0.3f, -1, 0); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ArrowButton.cpp ***/ /*** Start of inlined file: juce_Button.cpp ***/ BEGIN_JUCE_NAMESPACE class Button::RepeatTimer : public Timer { public: RepeatTimer (Button& owner_) : owner (owner_) {} void timerCallback() { owner.repeatTimerCallback(); } private: Button& owner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RepeatTimer); }; Button::Button (const String& name) : Component (name), text (name), buttonPressTime (0), lastRepeatTime (0), commandManagerToUse (nullptr), autoRepeatDelay (-1), autoRepeatSpeed (0), autoRepeatMinimumDelay (-1), radioGroupId (0), commandID (0), connectedEdgeFlags (0), buttonState (buttonNormal), lastToggleState (false), clickTogglesState (false), needsToRelease (false), needsRepainting (false), isKeyDown (false), triggerOnMouseDown (false), generateTooltip (false) { setWantsKeyboardFocus (true); isOn.addListener (this); } Button::~Button() { isOn.removeListener (this); if (commandManagerToUse != nullptr) commandManagerToUse->removeListener (this); repeatTimer = nullptr; clearShortcuts(); } void Button::setButtonText (const String& newText) { if (text != newText) { text = newText; repaint(); } } void Button::setTooltip (const String& newTooltip) { SettableTooltipClient::setTooltip (newTooltip); generateTooltip = false; } const String Button::getTooltip() { if (generateTooltip && commandManagerToUse != nullptr && commandID != 0) { String tt (commandManagerToUse->getDescriptionOfCommand (commandID)); Array keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID)); for (int i = 0; i < keyPresses.size(); ++i) { const String key (keyPresses.getReference(i).getTextDescription()); tt << " ["; if (key.length() == 1) tt << TRANS("shortcut") << ": '" << key << "']"; else tt << key << ']'; } return tt; } return SettableTooltipClient::getTooltip(); } void Button::setConnectedEdges (const int connectedEdgeFlags_) { if (connectedEdgeFlags != connectedEdgeFlags_) { connectedEdgeFlags = connectedEdgeFlags_; repaint(); } } void Button::setToggleState (const bool shouldBeOn, const bool sendChangeNotification) { if (shouldBeOn != lastToggleState) { if (isOn != shouldBeOn) // this test means that if the value is void rather than explicitly set to isOn = shouldBeOn; // false, it won't be changed unless the required value is true. lastToggleState = shouldBeOn; repaint(); WeakReference deletionWatcher (this); if (sendChangeNotification) { sendClickMessage (ModifierKeys()); if (deletionWatcher == nullptr) return; } if (lastToggleState) { turnOffOtherButtonsInGroup (sendChangeNotification); if (deletionWatcher == nullptr) return; } sendStateMessage(); } } void Button::setClickingTogglesState (const bool shouldToggle) noexcept { clickTogglesState = shouldToggle; // if you've got clickTogglesState turned on, you shouldn't also connect the button // up to be a command invoker. Instead, your command handler must flip the state of whatever // it is that this button represents, and the button will update its state to reflect this // in the applicationCommandListChanged() method. jassert (commandManagerToUse == nullptr || ! clickTogglesState); } bool Button::getClickingTogglesState() const noexcept { return clickTogglesState; } void Button::valueChanged (Value& value) { if (value.refersToSameSourceAs (isOn)) setToggleState (isOn.getValue(), true); } void Button::setRadioGroupId (const int newGroupId) { if (radioGroupId != newGroupId) { radioGroupId = newGroupId; if (lastToggleState) turnOffOtherButtonsInGroup (true); } } void Button::turnOffOtherButtonsInGroup (const bool sendChangeNotification) { Component* const p = getParentComponent(); if (p != nullptr && radioGroupId != 0) { WeakReference deletionWatcher (this); for (int i = p->getNumChildComponents(); --i >= 0;) { Component* const c = p->getChildComponent (i); if (c != this) { Button* const b = dynamic_cast (c); if (b != nullptr && b->getRadioGroupId() == radioGroupId) { b->setToggleState (false, sendChangeNotification); if (deletionWatcher == nullptr) return; } } } } } void Button::enablementChanged() { updateState(); repaint(); } Button::ButtonState Button::updateState() { return updateState (isMouseOver (true), isMouseButtonDown()); } Button::ButtonState Button::updateState (const bool over, const bool down) { ButtonState newState = buttonNormal; if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent()) { if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown) newState = buttonDown; else if (over) newState = buttonOver; } setState (newState); return newState; } void Button::setState (const ButtonState newState) { if (buttonState != newState) { buttonState = newState; repaint(); if (buttonState == buttonDown) { buttonPressTime = Time::getApproximateMillisecondCounter(); lastRepeatTime = 0; } sendStateMessage(); } } bool Button::isDown() const noexcept { return buttonState == buttonDown; } bool Button::isOver() const noexcept { return buttonState != buttonNormal; } void Button::buttonStateChanged() { } uint32 Button::getMillisecondsSinceButtonDown() const noexcept { const uint32 now = Time::getApproximateMillisecondCounter(); return now > buttonPressTime ? now - buttonPressTime : 0; } void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) noexcept { triggerOnMouseDown = isTriggeredOnMouseDown; } void Button::clicked() { } void Button::clicked (const ModifierKeys& /*modifiers*/) { clicked(); } static const int clickMessageId = 0x2f3f4f99; void Button::triggerClick() { postCommandMessage (clickMessageId); } void Button::internalClickCallback (const ModifierKeys& modifiers) { if (clickTogglesState) setToggleState ((radioGroupId != 0) || ! lastToggleState, false); sendClickMessage (modifiers); } void Button::flashButtonState() { if (isEnabled()) { needsToRelease = true; setState (buttonDown); getRepeatTimer().startTimer (100); } } void Button::handleCommandMessage (int commandId) { if (commandId == clickMessageId) { if (isEnabled()) { flashButtonState(); internalClickCallback (ModifierKeys::getCurrentModifiers()); } } else { Component::handleCommandMessage (commandId); } } void Button::addListener (ButtonListener* const newListener) { buttonListeners.add (newListener); } void Button::removeListener (ButtonListener* const listener) { buttonListeners.remove (listener); } void Button::addButtonListener (ButtonListener* l) { addListener (l); } void Button::removeButtonListener (ButtonListener* l) { removeListener (l); } void Button::sendClickMessage (const ModifierKeys& modifiers) { Component::BailOutChecker checker (this); if (commandManagerToUse != nullptr && commandID != 0) { ApplicationCommandTarget::InvocationInfo info (commandID); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton; info.originatingComponent = this; commandManagerToUse->invoke (info, true); } clicked (modifiers); if (! checker.shouldBailOut()) buttonListeners.callChecked (checker, &ButtonListener::buttonClicked, this); // (can't use Button::Listener due to idiotic VC2005 bug) } void Button::sendStateMessage() { Component::BailOutChecker checker (this); buttonStateChanged(); if (! checker.shouldBailOut()) buttonListeners.callChecked (checker, &ButtonListener::buttonStateChanged, this); } void Button::paint (Graphics& g) { if (needsToRelease && isEnabled()) { needsToRelease = false; needsRepainting = true; } paintButton (g, isOver(), isDown()); } void Button::mouseEnter (const MouseEvent&) { updateState (true, false); } void Button::mouseExit (const MouseEvent&) { updateState (false, false); } void Button::mouseDown (const MouseEvent& e) { updateState (true, true); if (isDown()) { if (autoRepeatDelay >= 0) getRepeatTimer().startTimer (autoRepeatDelay); if (triggerOnMouseDown) internalClickCallback (e.mods); } } void Button::mouseUp (const MouseEvent& e) { const bool wasDown = isDown(); updateState (isMouseOver(), false); if (wasDown && isOver() && ! triggerOnMouseDown) internalClickCallback (e.mods); } void Button::mouseDrag (const MouseEvent&) { const ButtonState oldState = buttonState; updateState (isMouseOver(), true); if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) getRepeatTimer().startTimer (autoRepeatSpeed); } void Button::focusGained (FocusChangeType) { updateState(); repaint(); } void Button::focusLost (FocusChangeType) { updateState(); repaint(); } void Button::visibilityChanged() { needsToRelease = false; updateState(); } void Button::parentHierarchyChanged() { Component* const newKeySource = (shortcuts.size() == 0) ? nullptr : getTopLevelComponent(); if (newKeySource != keySource.get()) { if (keySource != nullptr) keySource->removeKeyListener (this); keySource = newKeySource; if (keySource != nullptr) keySource->addKeyListener (this); } } void Button::setCommandToTrigger (ApplicationCommandManager* const commandManagerToUse_, const int commandID_, const bool generateTooltip_) { commandID = commandID_; generateTooltip = generateTooltip_; if (commandManagerToUse != commandManagerToUse_) { if (commandManagerToUse != nullptr) commandManagerToUse->removeListener (this); commandManagerToUse = commandManagerToUse_; if (commandManagerToUse != nullptr) commandManagerToUse->addListener (this); // if you've got clickTogglesState turned on, you shouldn't also connect the button // up to be a command invoker. Instead, your command handler must flip the state of whatever // it is that this button represents, and the button will update its state to reflect this // in the applicationCommandListChanged() method. jassert (commandManagerToUse == nullptr || ! clickTogglesState); } if (commandManagerToUse != nullptr) applicationCommandListChanged(); else setEnabled (true); } void Button::applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) { if (info.commandID == commandID && (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0) { flashButtonState(); } } void Button::applicationCommandListChanged() { if (commandManagerToUse != nullptr) { ApplicationCommandInfo info (0); ApplicationCommandTarget* const target = commandManagerToUse->getTargetForCommand (commandID, info); setEnabled (target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0); if (target != nullptr) setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, false); } } void Button::addShortcut (const KeyPress& key) { if (key.isValid()) { jassert (! isRegisteredForShortcut (key)); // already registered! shortcuts.add (key); parentHierarchyChanged(); } } void Button::clearShortcuts() { shortcuts.clear(); parentHierarchyChanged(); } bool Button::isShortcutPressed() const { if (! isCurrentlyBlockedByAnotherModalComponent()) { for (int i = shortcuts.size(); --i >= 0;) if (shortcuts.getReference(i).isCurrentlyDown()) return true; } return false; } bool Button::isRegisteredForShortcut (const KeyPress& key) const { for (int i = shortcuts.size(); --i >= 0;) if (key == shortcuts.getReference(i)) return true; return false; } bool Button::keyStateChanged (const bool, Component*) { if (! isEnabled()) return false; const bool wasDown = isKeyDown; isKeyDown = isShortcutPressed(); if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown)) getRepeatTimer().startTimer (autoRepeatDelay); updateState(); if (isEnabled() && wasDown && ! isKeyDown) { internalClickCallback (ModifierKeys::getCurrentModifiers()); // (return immediately - this button may now have been deleted) return true; } return wasDown || isKeyDown; } bool Button::keyPressed (const KeyPress&, Component*) { // returning true will avoid forwarding events for keys that we're using as shortcuts return isShortcutPressed(); } bool Button::keyPressed (const KeyPress& key) { if (isEnabled() && key.isKeyCode (KeyPress::returnKey)) { triggerClick(); return true; } return false; } void Button::setRepeatSpeed (const int initialDelayMillisecs, const int repeatMillisecs, const int minimumDelayInMillisecs) noexcept { autoRepeatDelay = initialDelayMillisecs; autoRepeatSpeed = repeatMillisecs; autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs); } void Button::repeatTimerCallback() { if (needsRepainting) { getRepeatTimer().stopTimer(); updateState(); needsRepainting = false; } else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown))) { int repeatSpeed = autoRepeatSpeed; if (autoRepeatMinimumDelay >= 0) { double timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0); timeHeldDown *= timeHeldDown; repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed)); } repeatSpeed = jmax (1, repeatSpeed); const uint32 now = Time::getMillisecondCounter(); // if we've been blocked from repeating often enough, speed up the repeat timer to compensate.. if (lastRepeatTime != 0 && (int) (now - lastRepeatTime) > repeatSpeed * 2) repeatSpeed = jmax (1, repeatSpeed / 2); lastRepeatTime = now; getRepeatTimer().startTimer (repeatSpeed); internalClickCallback (ModifierKeys::getCurrentModifiers()); } else if (! needsToRelease) { getRepeatTimer().stopTimer(); } } Button::RepeatTimer& Button::getRepeatTimer() { if (repeatTimer == nullptr) repeatTimer = new RepeatTimer (*this); return *repeatTimer; } END_JUCE_NAMESPACE /*** End of inlined file: juce_Button.cpp ***/ /*** Start of inlined file: juce_DrawableButton.cpp ***/ BEGIN_JUCE_NAMESPACE DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle) : Button (name), style (buttonStyle), currentImage (nullptr), edgeIndent (3) { if (buttonStyle == ImageOnButtonBackground) { backgroundOff = Colour (0xffbbbbff); backgroundOn = Colour (0xff3333ff); } else { backgroundOff = Colours::transparentBlack; backgroundOn = Colour (0xaabbbbff); } } DrawableButton::~DrawableButton() { } void DrawableButton::setImages (const Drawable* normal, const Drawable* over, const Drawable* down, const Drawable* disabled, const Drawable* normalOn, const Drawable* overOn, const Drawable* downOn, const Drawable* disabledOn) { jassert (normal != nullptr); // you really need to give it at least a normal image.. if (normal != nullptr) normalImage = normal->createCopy(); if (over != nullptr) overImage = over->createCopy(); if (down != nullptr) downImage = down->createCopy(); if (disabled != nullptr) disabledImage = disabled->createCopy(); if (normalOn != nullptr) normalImageOn = normalOn->createCopy(); if (overOn != nullptr) overImageOn = overOn->createCopy(); if (downOn != nullptr) downImageOn = downOn->createCopy(); if (disabledOn != nullptr) disabledImageOn = disabledOn->createCopy(); buttonStateChanged(); } void DrawableButton::setButtonStyle (const DrawableButton::ButtonStyle newStyle) { if (style != newStyle) { style = newStyle; buttonStateChanged(); } } void DrawableButton::setBackgroundColours (const Colour& toggledOffColour, const Colour& toggledOnColour) { if (backgroundOff != toggledOffColour || backgroundOn != toggledOnColour) { backgroundOff = toggledOffColour; backgroundOn = toggledOnColour; repaint(); } } const Colour& DrawableButton::getBackgroundColour() const noexcept { return getToggleState() ? backgroundOn : backgroundOff; } void DrawableButton::setEdgeIndent (const int numPixelsIndent) { edgeIndent = numPixelsIndent; repaint(); resized(); } void DrawableButton::resized() { Button::resized(); if (currentImage != nullptr) { if (style == ImageRaw) { currentImage->setOriginWithOriginalSize (Point()); } else { Rectangle imageSpace; if (style == ImageOnButtonBackground) { imageSpace = getLocalBounds().reduced (getWidth() / 4, getHeight() / 4); } else { const int textH = (style == ImageAboveTextLabel) ? jmin (16, proportionOfHeight (0.25f)) : 0; const int indentX = jmin (edgeIndent, proportionOfWidth (0.3f)); const int indentY = jmin (edgeIndent, proportionOfHeight (0.3f)); imageSpace.setBounds (indentX, indentY, getWidth() - indentX * 2, getHeight() - indentY * 2 - textH); } currentImage->setTransformToFit (imageSpace.toFloat(), RectanglePlacement::centred); } } } void DrawableButton::buttonStateChanged() { repaint(); Drawable* imageToDraw = nullptr; float opacity = 1.0f; if (isEnabled()) { imageToDraw = getCurrentImage(); } else { imageToDraw = getToggleState() ? disabledImageOn : disabledImage; if (imageToDraw == nullptr) { opacity = 0.4f; imageToDraw = getNormalImage(); } } if (imageToDraw != currentImage) { removeChildComponent (currentImage); currentImage = imageToDraw; if (currentImage != nullptr) { currentImage->setInterceptsMouseClicks (false, false); addAndMakeVisible (currentImage); DrawableButton::resized(); } } if (currentImage != nullptr) currentImage->setAlpha (opacity); } void DrawableButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { if (style == ImageOnButtonBackground) { getLookAndFeel().drawButtonBackground (g, *this, getBackgroundColour(), isMouseOverButton, isButtonDown); } else { g.fillAll (getBackgroundColour()); const int textH = (style == ImageAboveTextLabel) ? jmin (16, proportionOfHeight (0.25f)) : 0; if (textH > 0) { g.setFont ((float) textH); g.setColour (findColour (DrawableButton::textColourId) .withMultipliedAlpha (isEnabled() ? 1.0f : 0.4f)); g.drawFittedText (getButtonText(), 2, getHeight() - textH - 1, getWidth() - 4, textH, Justification::centred, 1); } } } Drawable* DrawableButton::getCurrentImage() const noexcept { if (isDown()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } Drawable* DrawableButton::getNormalImage() const noexcept { return (getToggleState() && normalImageOn != nullptr) ? normalImageOn : normalImage; } Drawable* DrawableButton::getOverImage() const noexcept { Drawable* d = normalImage; if (getToggleState()) { if (overImageOn != nullptr) d = overImageOn; else if (normalImageOn != nullptr) d = normalImageOn; else if (overImage != nullptr) d = overImage; } else { if (overImage != nullptr) d = overImage; } return d; } Drawable* DrawableButton::getDownImage() const noexcept { Drawable* d = normalImage; if (getToggleState()) { if (downImageOn != nullptr) d = downImageOn; else if (overImageOn != nullptr) d = overImageOn; else if (normalImageOn != nullptr) d = normalImageOn; else if (downImage != nullptr) d = downImage; else d = getOverImage(); } else { if (downImage != nullptr) d = downImage; else d = getOverImage(); } return d; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DrawableButton.cpp ***/ /*** Start of inlined file: juce_HyperlinkButton.cpp ***/ BEGIN_JUCE_NAMESPACE HyperlinkButton::HyperlinkButton (const String& linkText, const URL& linkURL) : Button (linkText), url (linkURL), font (14.0f, Font::underlined), resizeFont (true), justification (Justification::centred) { setMouseCursor (MouseCursor::PointingHandCursor); setTooltip (linkURL.toString (false)); } HyperlinkButton::~HyperlinkButton() { } void HyperlinkButton::setFont (const Font& newFont, const bool resizeToMatchComponentHeight, const Justification& justificationType) { font = newFont; resizeFont = resizeToMatchComponentHeight; justification = justificationType; repaint(); } void HyperlinkButton::setURL (const URL& newURL) noexcept { url = newURL; setTooltip (newURL.toString (false)); } Font HyperlinkButton::getFontToUse() const { Font f (font); if (resizeFont) f.setHeight (getHeight() * 0.7f); return f; } void HyperlinkButton::changeWidthToFitText() { setSize (getFontToUse().getStringWidth (getName()) + 6, getHeight()); } void HyperlinkButton::colourChanged() { repaint(); } void HyperlinkButton::clicked() { if (url.isWellFormed()) url.launchInDefaultBrowser(); } void HyperlinkButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { const Colour textColour (findColour (textColourId)); if (isEnabled()) g.setColour ((isMouseOverButton) ? textColour.darker ((isButtonDown) ? 1.3f : 0.4f) : textColour); else g.setColour (textColour.withMultipliedAlpha (0.4f)); g.setFont (getFontToUse()); g.drawText (getButtonText(), 2, 0, getWidth() - 2, getHeight(), justification.getOnlyHorizontalFlags() | Justification::verticallyCentred, true); } END_JUCE_NAMESPACE /*** End of inlined file: juce_HyperlinkButton.cpp ***/ /*** Start of inlined file: juce_ImageButton.cpp ***/ BEGIN_JUCE_NAMESPACE ImageButton::ImageButton (const String& text_) : Button (text_), scaleImageToFit (true), preserveProportions (true), alphaThreshold (0), imageX (0), imageY (0), imageW (0), imageH (0) { } ImageButton::~ImageButton() { } void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, const bool rescaleImagesWhenButtonSizeChanges, const bool preserveImageProportions, const Image& normalImage_, const float imageOpacityWhenNormal, const Colour& overlayColourWhenNormal, const Image& overImage_, const float imageOpacityWhenOver, const Colour& overlayColourWhenOver, const Image& downImage_, const float imageOpacityWhenDown, const Colour& overlayColourWhenDown, const float hitTestAlphaThreshold) { normalImage = normalImage_; overImage = overImage_; downImage = downImage_; if (resizeButtonNowToFitThisImage && normalImage.isValid()) { imageW = normalImage.getWidth(); imageH = normalImage.getHeight(); setSize (imageW, imageH); } scaleImageToFit = rescaleImagesWhenButtonSizeChanges; preserveProportions = preserveImageProportions; normalOpacity = imageOpacityWhenNormal; normalOverlay = overlayColourWhenNormal; overOpacity = imageOpacityWhenOver; overOverlay = overlayColourWhenOver; downOpacity = imageOpacityWhenDown; downOverlay = overlayColourWhenDown; alphaThreshold = (unsigned char) jlimit (0, 0xff, roundToInt (255.0f * hitTestAlphaThreshold)); repaint(); } Image ImageButton::getCurrentImage() const { if (isDown() || getToggleState()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } Image ImageButton::getNormalImage() const { return normalImage; } Image ImageButton::getOverImage() const { return overImage.isValid() ? overImage : normalImage; } Image ImageButton::getDownImage() const { return downImage.isValid() ? downImage : getOverImage(); } void ImageButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { if (! isEnabled()) { isMouseOverButton = false; isButtonDown = false; } Image im (getCurrentImage()); if (im.isValid()) { const int iw = im.getWidth(); const int ih = im.getHeight(); imageW = getWidth(); imageH = getHeight(); imageX = (imageW - iw) >> 1; imageY = (imageH - ih) >> 1; if (scaleImageToFit) { if (preserveProportions) { int newW, newH; const float imRatio = ih / (float)iw; const float destRatio = imageH / (float)imageW; if (imRatio > destRatio) { newW = roundToInt (imageH / imRatio); newH = imageH; } else { newW = imageW; newH = roundToInt (imageW * imRatio); } imageX = (imageW - newW) / 2; imageY = (imageH - newH) / 2; imageW = newW; imageH = newH; } else { imageX = 0; imageY = 0; } } if (! scaleImageToFit) { imageW = iw; imageH = ih; } getLookAndFeel().drawImageButton (g, &im, imageX, imageY, imageW, imageH, isButtonDown ? downOverlay : (isMouseOverButton ? overOverlay : normalOverlay), isButtonDown ? downOpacity : (isMouseOverButton ? overOpacity : normalOpacity), *this); } } bool ImageButton::hitTest (int x, int y) { if (alphaThreshold == 0) return true; Image im (getCurrentImage()); return im.isNull() || (imageW > 0 && imageH > 0 && alphaThreshold < im.getPixelAt (((x - imageX) * im.getWidth()) / imageW, ((y - imageY) * im.getHeight()) / imageH).getAlpha()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ImageButton.cpp ***/ /*** Start of inlined file: juce_ShapeButton.cpp ***/ BEGIN_JUCE_NAMESPACE ShapeButton::ShapeButton (const String& text_, const Colour& normalColour_, const Colour& overColour_, const Colour& downColour_) : Button (text_), normalColour (normalColour_), overColour (overColour_), downColour (downColour_), maintainShapeProportions (false), outlineWidth (0.0f) { } ShapeButton::~ShapeButton() { } void ShapeButton::setColours (const Colour& newNormalColour, const Colour& newOverColour, const Colour& newDownColour) { normalColour = newNormalColour; overColour = newOverColour; downColour = newDownColour; } void ShapeButton::setOutline (const Colour& newOutlineColour, const float newOutlineWidth) { outlineColour = newOutlineColour; outlineWidth = newOutlineWidth; } void ShapeButton::setShape (const Path& newShape, const bool resizeNowToFitThisShape, const bool maintainShapeProportions_, const bool hasShadow) { shape = newShape; maintainShapeProportions = maintainShapeProportions_; shadow.setShadowProperties (3.0f, 0.5f, 0, 0); setComponentEffect ((hasShadow) ? &shadow : 0); if (resizeNowToFitThisShape) { Rectangle newBounds (shape.getBounds()); if (hasShadow) newBounds.expand (4.0f, 4.0f); shape.applyTransform (AffineTransform::translation (-newBounds.getX(), -newBounds.getY())); setSize (1 + (int) (newBounds.getWidth() + outlineWidth), 1 + (int) (newBounds.getHeight() + outlineWidth)); } } void ShapeButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { if (! isEnabled()) { isMouseOverButton = false; isButtonDown = false; } g.setColour ((isButtonDown) ? downColour : (isMouseOverButton) ? overColour : normalColour); int w = getWidth(); int h = getHeight(); if (getComponentEffect() != nullptr) { w -= 4; h -= 4; } const float offset = (outlineWidth * 0.5f) + (isButtonDown ? 1.5f : 0.0f); const AffineTransform trans (shape.getTransformToScaleToFit (offset, offset, w - offset - outlineWidth, h - offset - outlineWidth, maintainShapeProportions)); g.fillPath (shape, trans); if (outlineWidth > 0.0f) { g.setColour (outlineColour); g.strokePath (shape, PathStrokeType (outlineWidth), trans); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ShapeButton.cpp ***/ /*** Start of inlined file: juce_TextButton.cpp ***/ BEGIN_JUCE_NAMESPACE TextButton::TextButton (const String& name, const String& toolTip) : Button (name) { setTooltip (toolTip); } TextButton::~TextButton() { } void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { getLookAndFeel().drawButtonBackground (g, *this, findColour (getToggleState() ? buttonOnColourId : buttonColourId), isMouseOverButton, isButtonDown); getLookAndFeel().drawButtonText (g, *this, isMouseOverButton, isButtonDown); } void TextButton::colourChanged() { repaint(); } const Font TextButton::getFont() { return Font (jmin (15.0f, getHeight() * 0.6f)); } void TextButton::changeWidthToFitText (const int newHeight) { if (newHeight >= 0) setSize (jmax (1, getWidth()), newHeight); setSize (getFont().getStringWidth (getButtonText()) + getHeight(), getHeight()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_TextButton.cpp ***/ /*** Start of inlined file: juce_ToggleButton.cpp ***/ BEGIN_JUCE_NAMESPACE ToggleButton::ToggleButton (const String& buttonText) : Button (buttonText) { setClickingTogglesState (true); } ToggleButton::~ToggleButton() { } void ToggleButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { getLookAndFeel().drawToggleButton (g, *this, isMouseOverButton, isButtonDown); } void ToggleButton::changeWidthToFitText() { getLookAndFeel().changeToggleButtonWidthToFitText (*this); } void ToggleButton::colourChanged() { repaint(); } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToggleButton.cpp ***/ /*** Start of inlined file: juce_ToolbarButton.cpp ***/ BEGIN_JUCE_NAMESPACE ToolbarButton::ToolbarButton (const int itemId_, const String& buttonText, Drawable* const normalImage_, Drawable* const toggledOnImage_) : ToolbarItemComponent (itemId_, buttonText, true), normalImage (normalImage_), toggledOnImage (toggledOnImage_), currentImage (nullptr) { jassert (normalImage_ != nullptr); } ToolbarButton::~ToolbarButton() { } bool ToolbarButton::getToolbarItemSizes (int toolbarDepth, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize) { preferredSize = minSize = maxSize = toolbarDepth; return true; } void ToolbarButton::paintButtonArea (Graphics&, int /*width*/, int /*height*/, bool /*isMouseOver*/, bool /*isMouseDown*/) { } void ToolbarButton::contentAreaChanged (const Rectangle&) { buttonStateChanged(); } void ToolbarButton::updateDrawable() { if (currentImage != nullptr) { currentImage->setTransformToFit (getContentArea().toFloat(), RectanglePlacement::centred); currentImage->setAlpha (isEnabled() ? 1.0f : 0.5f); } } void ToolbarButton::resized() { ToolbarItemComponent::resized(); updateDrawable(); } void ToolbarButton::enablementChanged() { ToolbarItemComponent::enablementChanged(); updateDrawable(); } void ToolbarButton::buttonStateChanged() { Drawable* d = normalImage; if (getToggleState() && toggledOnImage != nullptr) d = toggledOnImage; if (d != currentImage) { removeChildComponent (currentImage); currentImage = d; if (d != nullptr) { enablementChanged(); addAndMakeVisible (d); updateDrawable(); } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_ToolbarButton.cpp ***/ /*** Start of inlined file: juce_CodeDocument.cpp ***/ BEGIN_JUCE_NAMESPACE class CodeDocumentLine { public: CodeDocumentLine (const String::CharPointerType& line_, const int lineLength_, const int numNewLineChars, const int lineStartInFile_) : line (line_, lineLength_), lineStartInFile (lineStartInFile_), lineLength (lineLength_), lineLengthWithoutNewLines (lineLength_ - numNewLineChars) { } static void createLines (Array & newLines, const String& text) { String::CharPointerType t (text.getCharPointer()); int charNumInFile = 0; bool finished = false; while (! (finished || t.isEmpty())) { String::CharPointerType startOfLine (t); int startOfLineInFile = charNumInFile; int lineLength = 0; int numNewLineChars = 0; for (;;) { const juce_wchar c = t.getAndAdvance(); if (c == 0) { finished = true; break; } ++charNumInFile; ++lineLength; if (c == '\r') { ++numNewLineChars; if (*t == '\n') { ++t; ++charNumInFile; ++lineLength; ++numNewLineChars; } break; } if (c == '\n') { ++numNewLineChars; break; } } newLines.add (new CodeDocumentLine (startOfLine, lineLength, numNewLineChars, startOfLineInFile)); } jassert (charNumInFile == text.length()); } bool endsWithLineBreak() const noexcept { return lineLengthWithoutNewLines != lineLength; } void updateLength() noexcept { lineLength = 0; lineLengthWithoutNewLines = 0; String::CharPointerType t (line.getCharPointer()); for (;;) { const juce_wchar c = t.getAndAdvance(); if (c == 0) break; ++lineLength; if (c != '\n' && c != '\r') lineLengthWithoutNewLines = lineLength; } } String line; int lineStartInFile, lineLength, lineLengthWithoutNewLines; }; CodeDocument::Iterator::Iterator (CodeDocument* const document_) : document (document_), charPointer (nullptr), line (0), position (0) { } CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) : document (other.document), charPointer (other.charPointer), line (other.line), position (other.position) { } CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept { document = other.document; charPointer = other.charPointer; line = other.line; position = other.position; return *this; } CodeDocument::Iterator::~Iterator() noexcept { } juce_wchar CodeDocument::Iterator::nextChar() { for (;;) { if (charPointer.getAddress() == nullptr) { CodeDocumentLine* const l = document->lines[line]; if (l == nullptr) return 0; charPointer = l->line.getCharPointer(); } const juce_wchar result = charPointer.getAndAdvance(); if (result == 0) { ++line; charPointer = nullptr; } else { ++position; return result; } } } void CodeDocument::Iterator::skip() { nextChar(); } void CodeDocument::Iterator::skipToEndOfLine() { if (charPointer.getAddress() == nullptr) { CodeDocumentLine* const l = document->lines[line]; if (l == nullptr) return; charPointer = l->line.getCharPointer(); } position += charPointer.length(); ++line; charPointer = nullptr; } juce_wchar CodeDocument::Iterator::peekNextChar() const { if (charPointer.getAddress() == nullptr) { CodeDocumentLine* const l = document->lines[line]; if (l == nullptr) return 0; charPointer = l->line.getCharPointer(); } const juce_wchar c = *charPointer; if (c != 0) return c; CodeDocumentLine* const l = document->lines [line + 1]; return l == nullptr ? 0 : l->line[0]; } void CodeDocument::Iterator::skipWhitespace() { while (CharacterFunctions::isWhitespace (peekNextChar())) skip(); } bool CodeDocument::Iterator::isEOF() const noexcept { return charPointer.getAddress() == nullptr && line >= document->lines.size(); } CodeDocument::Position::Position() noexcept : owner (0), characterPos (0), line (0), indexInLine (0), positionMaintained (false) { } CodeDocument::Position::Position (const CodeDocument* const ownerDocument, const int line_, const int indexInLine_) noexcept : owner (const_cast (ownerDocument)), characterPos (0), line (line_), indexInLine (indexInLine_), positionMaintained (false) { setLineAndIndex (line_, indexInLine_); } CodeDocument::Position::Position (const CodeDocument* const ownerDocument, const int characterPos_) noexcept : owner (const_cast (ownerDocument)), positionMaintained (false) { setPosition (characterPos_); } CodeDocument::Position::Position (const Position& other) noexcept : owner (other.owner), characterPos (other.characterPos), line (other.line), indexInLine (other.indexInLine), positionMaintained (false) { jassert (*this == other); } CodeDocument::Position::~Position() { setPositionMaintained (false); } CodeDocument::Position& CodeDocument::Position::operator= (const Position& other) { if (this != &other) { const bool wasPositionMaintained = positionMaintained; if (owner != other.owner) setPositionMaintained (false); owner = other.owner; line = other.line; indexInLine = other.indexInLine; characterPos = other.characterPos; setPositionMaintained (wasPositionMaintained); jassert (*this == other); } return *this; } bool CodeDocument::Position::operator== (const Position& other) const noexcept { jassert ((characterPos == other.characterPos) == (line == other.line && indexInLine == other.indexInLine)); return characterPos == other.characterPos && line == other.line && indexInLine == other.indexInLine && owner == other.owner; } bool CodeDocument::Position::operator!= (const Position& other) const noexcept { return ! operator== (other); } void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine) { jassert (owner != nullptr); if (owner->lines.size() == 0) { line = 0; indexInLine = 0; characterPos = 0; } else { if (newLineNum >= owner->lines.size()) { line = owner->lines.size() - 1; CodeDocumentLine* const l = owner->lines.getUnchecked (line); jassert (l != nullptr); indexInLine = l->lineLengthWithoutNewLines; characterPos = l->lineStartInFile + indexInLine; } else { line = jmax (0, newLineNum); CodeDocumentLine* const l = owner->lines.getUnchecked (line); jassert (l != nullptr); if (l->lineLengthWithoutNewLines > 0) indexInLine = jlimit (0, l->lineLengthWithoutNewLines, newIndexInLine); else indexInLine = 0; characterPos = l->lineStartInFile + indexInLine; } } } void CodeDocument::Position::setPosition (const int newPosition) { jassert (owner != nullptr); line = 0; indexInLine = 0; characterPos = 0; if (newPosition > 0) { int lineStart = 0; int lineEnd = owner->lines.size(); for (;;) { if (lineEnd - lineStart < 4) { for (int i = lineStart; i < lineEnd; ++i) { CodeDocumentLine* const l = owner->lines.getUnchecked (i); int index = newPosition - l->lineStartInFile; if (index >= 0 && (index < l->lineLength || i == lineEnd - 1)) { line = i; indexInLine = jmin (l->lineLengthWithoutNewLines, index); characterPos = l->lineStartInFile + indexInLine; } } break; } else { const int midIndex = (lineStart + lineEnd + 1) / 2; CodeDocumentLine* const mid = owner->lines.getUnchecked (midIndex); if (newPosition >= mid->lineStartInFile) lineStart = midIndex; else lineEnd = midIndex; } } } } void CodeDocument::Position::moveBy (int characterDelta) { jassert (owner != nullptr); if (characterDelta == 1) { setPosition (getPosition()); // If moving right, make sure we don't get stuck between the \r and \n characters.. if (line < owner->lines.size()) { CodeDocumentLine* const l = owner->lines.getUnchecked (line); if (indexInLine + characterDelta < l->lineLength && indexInLine + characterDelta >= l->lineLengthWithoutNewLines + 1) ++characterDelta; } } setPosition (characterPos + characterDelta); } const CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const { CodeDocument::Position p (*this); p.moveBy (characterDelta); return p; } const CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const { CodeDocument::Position p (*this); p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine()); return p; } const juce_wchar CodeDocument::Position::getCharacter() const { const CodeDocumentLine* const l = owner->lines [line]; return l == nullptr ? 0 : l->line [getIndexInLine()]; } const String CodeDocument::Position::getLineText() const { const CodeDocumentLine* const l = owner->lines [line]; return l == nullptr ? String::empty : l->line; } void CodeDocument::Position::setPositionMaintained (const bool isMaintained) { if (isMaintained != positionMaintained) { positionMaintained = isMaintained; if (owner != nullptr) { if (isMaintained) { jassert (! owner->positionsToMaintain.contains (this)); owner->positionsToMaintain.add (this); } else { // If this happens, you may have deleted the document while there are Position objects that are still using it... jassert (owner->positionsToMaintain.contains (this)); owner->positionsToMaintain.removeValue (this); } } } } CodeDocument::CodeDocument() : undoManager (std::numeric_limits::max(), 10000), currentActionIndex (0), indexOfSavedState (-1), maximumLineLength (-1), newLineChars ("\r\n") { } CodeDocument::~CodeDocument() { } const String CodeDocument::getAllContent() const { return getTextBetween (Position (this, 0), Position (this, lines.size(), 0)); } const String CodeDocument::getTextBetween (const Position& start, const Position& end) const { if (end.getPosition() <= start.getPosition()) return String::empty; const int startLine = start.getLineNumber(); const int endLine = end.getLineNumber(); if (startLine == endLine) { CodeDocumentLine* const line = lines [startLine]; return (line == nullptr) ? String::empty : line->line.substring (start.getIndexInLine(), end.getIndexInLine()); } MemoryOutputStream mo; mo.preallocate (end.getPosition() - start.getPosition() + 4); const int maxLine = jmin (lines.size() - 1, endLine); for (int i = jmax (0, startLine); i <= maxLine; ++i) { const CodeDocumentLine* line = lines.getUnchecked(i); int len = line->lineLength; if (i == startLine) { const int index = start.getIndexInLine(); mo << line->line.substring (index, len); } else if (i == endLine) { len = end.getIndexInLine(); mo << line->line.substring (0, len); } else { mo << line->line; } } return mo.toString(); } int CodeDocument::getNumCharacters() const noexcept { const CodeDocumentLine* const lastLine = lines.getLast(); return (lastLine == nullptr) ? 0 : lastLine->lineStartInFile + lastLine->lineLength; } const String CodeDocument::getLine (const int lineIndex) const noexcept { const CodeDocumentLine* const line = lines [lineIndex]; return (line == nullptr) ? String::empty : line->line; } int CodeDocument::getMaximumLineLength() noexcept { if (maximumLineLength < 0) { maximumLineLength = 0; for (int i = lines.size(); --i >= 0;) maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength); } return maximumLineLength; } void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition) { remove (startPosition.getPosition(), endPosition.getPosition(), true); } void CodeDocument::insertText (const Position& position, const String& text) { insert (text, position.getPosition(), true); } void CodeDocument::replaceAllContent (const String& newContent) { remove (0, getNumCharacters(), true); insert (newContent, 0, true); } bool CodeDocument::loadFromStream (InputStream& stream) { remove (0, getNumCharacters(), false); insert (stream.readEntireStreamAsString(), 0, false); setSavePoint(); clearUndoHistory(); return true; } bool CodeDocument::writeToStream (OutputStream& stream) { for (int i = 0; i < lines.size(); ++i) { String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string. const char* utf8 = temp.toUTF8(); if (! stream.write (utf8, (int) strlen (utf8))) return false; } return true; } void CodeDocument::setNewLineCharacters (const String& newLineChars_) noexcept { jassert (newLineChars_ == "\r\n" || newLineChars_ == "\n" || newLineChars_ == "\r"); newLineChars = newLineChars_; } void CodeDocument::newTransaction() { undoManager.beginNewTransaction (String::empty); } void CodeDocument::undo() { newTransaction(); undoManager.undo(); } void CodeDocument::redo() { undoManager.redo(); } void CodeDocument::clearUndoHistory() { undoManager.clearUndoHistory(); } void CodeDocument::setSavePoint() noexcept { indexOfSavedState = currentActionIndex; } bool CodeDocument::hasChangedSinceSavePoint() const noexcept { return currentActionIndex != indexOfSavedState; } namespace CodeDocumentHelpers { int getCharacterType (const juce_wchar character) noexcept { return (CharacterFunctions::isLetterOrDigit (character) || character == '_') ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); } } const CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept { Position p (position); const int maxDistance = 256; int i = 0; while (i < maxDistance && CharacterFunctions::isWhitespace (p.getCharacter()) && (i == 0 || (p.getCharacter() != '\n' && p.getCharacter() != '\r'))) { ++i; p.moveBy (1); } if (i == 0) { const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter()); while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter())) { ++i; p.moveBy (1); } while (i < maxDistance && CharacterFunctions::isWhitespace (p.getCharacter()) && (i == 0 || (p.getCharacter() != '\n' && p.getCharacter() != '\r'))) { ++i; p.moveBy (1); } } return p; } const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept { Position p (position); const int maxDistance = 256; int i = 0; bool stoppedAtLineStart = false; while (i < maxDistance) { const juce_wchar c = p.movedBy (-1).getCharacter(); if (c == '\r' || c == '\n') { stoppedAtLineStart = true; if (i > 0) break; } if (! CharacterFunctions::isWhitespace (c)) break; p.moveBy (-1); ++i; } if (i < maxDistance && ! stoppedAtLineStart) { const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()); while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter())) { p.moveBy (-1); ++i; } } return p; } void CodeDocument::checkLastLineStatus() { while (lines.size() > 0 && lines.getLast()->lineLength == 0 && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak())) { // remove any empty lines at the end if the preceding line doesn't end in a newline. lines.removeLast(); } const CodeDocumentLine* const lastLine = lines.getLast(); if (lastLine != nullptr && lastLine->endsWithLineBreak()) { // check that there's an empty line at the end if the preceding one ends in a newline.. lines.add (new CodeDocumentLine (String::empty.getCharPointer(), 0, 0, lastLine->lineStartInFile + lastLine->lineLength)); } } void CodeDocument::addListener (CodeDocument::Listener* const listener) noexcept { listeners.add (listener); } void CodeDocument::removeListener (CodeDocument::Listener* const listener) noexcept { listeners.remove (listener); } void CodeDocument::sendListenerChangeMessage (const int startLine, const int endLine) { Position startPos (this, startLine, 0); Position endPos (this, endLine, 0); listeners.call (&CodeDocument::Listener::codeDocumentChanged, startPos, endPos); } class CodeDocumentInsertAction : public UndoableAction { public: CodeDocumentInsertAction (CodeDocument& owner_, const String& text_, const int insertPos_) noexcept : owner (owner_), text (text_), insertPos (insertPos_) { } bool perform() { owner.currentActionIndex++; owner.insert (text, insertPos, false); return true; } bool undo() { owner.currentActionIndex--; owner.remove (insertPos, insertPos + text.length(), false); return true; } int getSizeInUnits() { return text.length() + 32; } private: CodeDocument& owner; const String text; int insertPos; JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction); }; void CodeDocument::insert (const String& text, const int insertPos, const bool undoable) { if (text.isEmpty()) return; if (undoable) { undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos)); } else { Position pos (this, insertPos); const int firstAffectedLine = pos.getLineNumber(); int lastAffectedLine = firstAffectedLine + 1; CodeDocumentLine* const firstLine = lines [firstAffectedLine]; String textInsideOriginalLine (text); if (firstLine != nullptr) { const int index = pos.getIndexInLine(); textInsideOriginalLine = firstLine->line.substring (0, index) + textInsideOriginalLine + firstLine->line.substring (index); } maximumLineLength = -1; Array newLines; CodeDocumentLine::createLines (newLines, textInsideOriginalLine); jassert (newLines.size() > 0); CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0); newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0; lines.set (firstAffectedLine, newFirstLine); if (newLines.size() > 1) { for (int i = 1; i < newLines.size(); ++i) { CodeDocumentLine* const l = newLines.getUnchecked (i); lines.insert (firstAffectedLine + i, l); } lastAffectedLine = lines.size(); } int i, lineStart = newFirstLine->lineStartInFile; for (i = firstAffectedLine; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); l->lineStartInFile = lineStart; lineStart += l->lineLength; } checkLastLineStatus(); const int newTextLength = text.length(); for (i = 0; i < positionsToMaintain.size(); ++i) { CodeDocument::Position* const p = positionsToMaintain.getUnchecked(i); if (p->getPosition() >= insertPos) p->setPosition (p->getPosition() + newTextLength); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); } } class CodeDocumentDeleteAction : public UndoableAction { public: CodeDocumentDeleteAction (CodeDocument& owner_, const int startPos_, const int endPos_) noexcept : owner (owner_), startPos (startPos_), endPos (endPos_) { removedText = owner.getTextBetween (CodeDocument::Position (&owner, startPos), CodeDocument::Position (&owner, endPos)); } bool perform() { owner.currentActionIndex++; owner.remove (startPos, endPos, false); return true; } bool undo() { owner.currentActionIndex--; owner.insert (removedText, startPos, false); return true; } int getSizeInUnits() { return removedText.length() + 32; } private: CodeDocument& owner; int startPos, endPos; String removedText; JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction); }; void CodeDocument::remove (const int startPos, const int endPos, const bool undoable) { if (endPos <= startPos) return; if (undoable) { undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos)); } else { Position startPosition (this, startPos); Position endPosition (this, endPos); maximumLineLength = -1; const int firstAffectedLine = startPosition.getLineNumber(); const int endLine = endPosition.getLineNumber(); int lastAffectedLine = firstAffectedLine + 1; CodeDocumentLine* const firstLine = lines.getUnchecked (firstAffectedLine); if (firstAffectedLine == endLine) { firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine()) + firstLine->line.substring (endPosition.getIndexInLine()); firstLine->updateLength(); } else { lastAffectedLine = lines.size(); CodeDocumentLine* const lastLine = lines.getUnchecked (endLine); jassert (lastLine != nullptr); firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine()) + lastLine->line.substring (endPosition.getIndexInLine()); firstLine->updateLength(); int numLinesToRemove = endLine - firstAffectedLine; lines.removeRange (firstAffectedLine + 1, numLinesToRemove); } int i; for (i = firstAffectedLine + 1; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); const CodeDocumentLine* const previousLine = lines.getUnchecked (i - 1); l->lineStartInFile = previousLine->lineStartInFile + previousLine->lineLength; } checkLastLineStatus(); const int totalChars = getNumCharacters(); for (i = 0; i < positionsToMaintain.size(); ++i) { CodeDocument::Position* p = positionsToMaintain.getUnchecked(i); if (p->getPosition() > startPosition.getPosition()) p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos)); if (p->getPosition() > totalChars) p->setPosition (totalChars); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_CodeDocument.cpp ***/ /*** Start of inlined file: juce_CodeEditorComponent.cpp ***/ BEGIN_JUCE_NAMESPACE class CodeEditorComponent::CodeEditorLine { public: CodeEditorLine() noexcept : highlightColumnStart (0), highlightColumnEnd (0) { } bool update (CodeDocument& document, int lineNum, CodeDocument::Iterator& source, CodeTokeniser* analyser, const int spacesPerTab, const CodeDocument::Position& selectionStart, const CodeDocument::Position& selectionEnd) { Array newTokens; newTokens.ensureStorageAllocated (8); if (analyser == nullptr) { newTokens.add (SyntaxToken (document.getLine (lineNum), -1)); } else if (lineNum < document.getNumLines()) { const CodeDocument::Position pos (&document, lineNum, 0); createTokens (pos.getPosition(), pos.getLineText(), source, analyser, newTokens); } replaceTabsWithSpaces (newTokens, spacesPerTab); int newHighlightStart = 0; int newHighlightEnd = 0; if (selectionStart.getLineNumber() <= lineNum && selectionEnd.getLineNumber() >= lineNum) { const String line (document.getLine (lineNum)); CodeDocument::Position lineStart (&document, lineNum, 0), lineEnd (&document, lineNum + 1, 0); newHighlightStart = indexToColumn (jmax (0, selectionStart.getPosition() - lineStart.getPosition()), line, spacesPerTab); newHighlightEnd = indexToColumn (jmin (lineEnd.getPosition() - lineStart.getPosition(), selectionEnd.getPosition() - lineStart.getPosition()), line, spacesPerTab); } if (newHighlightStart != highlightColumnStart || newHighlightEnd != highlightColumnEnd) { highlightColumnStart = newHighlightStart; highlightColumnEnd = newHighlightEnd; } else { if (tokens.size() == newTokens.size()) { bool allTheSame = true; for (int i = newTokens.size(); --i >= 0;) { if (tokens.getReference(i) != newTokens.getReference(i)) { allTheSame = false; break; } } if (allTheSame) return false; } } tokens.swapWithArray (newTokens); return true; } void draw (CodeEditorComponent& owner, Graphics& g, const Font& font, float x, const int y, const int baselineOffset, const int lineHeight, const Colour& highlightColour) const { if (highlightColumnStart < highlightColumnEnd) { g.setColour (highlightColour); g.fillRect (roundToInt (x + highlightColumnStart * owner.getCharWidth()), y, roundToInt ((highlightColumnEnd - highlightColumnStart) * owner.getCharWidth()), lineHeight); } int lastType = std::numeric_limits::min(); for (int i = 0; i < tokens.size(); ++i) { SyntaxToken& token = tokens.getReference(i); if (lastType != token.tokenType) { lastType = token.tokenType; g.setColour (owner.getColourForTokenType (lastType)); } g.drawSingleLineText (token.text, roundToInt (x), y + baselineOffset); if (i < tokens.size() - 1) { if (token.width < 0) token.width = font.getStringWidthFloat (token.text); x += token.width; } } } private: struct SyntaxToken { SyntaxToken (const String& text_, const int type) noexcept : text (text_), tokenType (type), width (-1.0f) { } bool operator!= (const SyntaxToken& other) const noexcept { return text != other.text || tokenType != other.tokenType; } String text; int tokenType; float width; }; Array tokens; int highlightColumnStart, highlightColumnEnd; static void createTokens (int startPosition, const String& lineText, CodeDocument::Iterator& source, CodeTokeniser* analyser, Array & newTokens) { CodeDocument::Iterator lastIterator (source); const int lineLength = lineText.length(); for (;;) { int tokenType = analyser->readNextToken (source); int tokenStart = lastIterator.getPosition(); int tokenEnd = source.getPosition(); if (tokenEnd <= tokenStart) break; tokenEnd -= startPosition; if (tokenEnd > 0) { tokenStart -= startPosition; newTokens.add (SyntaxToken (lineText.substring (jmax (0, tokenStart), tokenEnd), tokenType)); if (tokenEnd >= lineLength) break; } lastIterator = source; } source = lastIterator; } static void replaceTabsWithSpaces (Array & tokens, const int spacesPerTab) { int x = 0; for (int i = 0; i < tokens.size(); ++i) { SyntaxToken& t = tokens.getReference(i); for (;;) { int tabPos = t.text.indexOfChar ('\t'); if (tabPos < 0) break; const int spacesNeeded = spacesPerTab - ((tabPos + x) % spacesPerTab); t.text = t.text.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded)); } x += t.text.length(); } } int indexToColumn (int index, const String& line, int spacesPerTab) const noexcept { jassert (index <= line.length()); String::CharPointerType t (line.getCharPointer()); int col = 0; for (int i = 0; i < index; ++i) { if (t.getAndAdvance() != '\t') ++col; else col += spacesPerTab - (col % spacesPerTab); } return col; } }; CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, CodeTokeniser* const codeTokeniser_) : document (document_), firstLineOnScreen (0), gutter (5), spacesPerTab (4), lineHeight (0), linesOnScreen (0), columnsOnScreen (0), scrollbarThickness (16), columnToTryToMaintain (-1), useSpacesForTabs (false), xOffset (0), verticalScrollBar (true), horizontalScrollBar (false), codeTokeniser (codeTokeniser_) { caretPos = CodeDocument::Position (&document_, 0, 0); caretPos.setPositionMaintained (true); selectionStart = CodeDocument::Position (&document_, 0, 0); selectionStart.setPositionMaintained (true); selectionEnd = CodeDocument::Position (&document_, 0, 0); selectionEnd.setPositionMaintained (true); setOpaque (true); setMouseCursor (MouseCursor (MouseCursor::IBeamCursor)); setWantsKeyboardFocus (true); addAndMakeVisible (&verticalScrollBar); verticalScrollBar.setSingleStepSize (1.0); addAndMakeVisible (&horizontalScrollBar); horizontalScrollBar.setSingleStepSize (1.0); addAndMakeVisible (caret = getLookAndFeel().createCaretComponent (this)); Font f (12.0f); f.setTypefaceName (Font::getDefaultMonospacedFontName()); setFont (f); resetToDefaultColours(); verticalScrollBar.addListener (this); horizontalScrollBar.addListener (this); document.addListener (this); } CodeEditorComponent::~CodeEditorComponent() { document.removeListener (this); } void CodeEditorComponent::loadContent (const String& newContent) { clearCachedIterators (0); document.replaceAllContent (newContent); document.clearUndoHistory(); document.setSavePoint(); caretPos.setPosition (0); selectionStart.setPosition (0); selectionEnd.setPosition (0); scrollToLine (0); } bool CodeEditorComponent::isTextInputActive() const { return true; } void CodeEditorComponent::setTemporaryUnderlining (const Array >&) { jassertfalse; // TODO Windows IME not yet supported for this comp.. } const Rectangle CodeEditorComponent::getCaretRectangle() { return getLocalArea (caret, caret->getLocalBounds()); } void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) { clearCachedIterators (affectedTextStart.getLineNumber()); triggerAsyncUpdate(); updateCaretPosition(); columnToTryToMaintain = -1; if (affectedTextEnd.getPosition() >= selectionStart.getPosition() && affectedTextStart.getPosition() <= selectionEnd.getPosition()) deselectAll(); if (caretPos.getPosition() > affectedTextEnd.getPosition() || caretPos.getPosition() < affectedTextStart.getPosition()) moveCaretTo (affectedTextStart, false); updateScrollBars(); } void CodeEditorComponent::resized() { linesOnScreen = (getHeight() - scrollbarThickness) / lineHeight; columnsOnScreen = (int) ((getWidth() - scrollbarThickness) / charWidth); lines.clear(); rebuildLineTokens(); updateCaretPosition(); verticalScrollBar.setBounds (getWidth() - scrollbarThickness, 0, scrollbarThickness, getHeight() - scrollbarThickness); horizontalScrollBar.setBounds (gutter, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness - gutter, scrollbarThickness); updateScrollBars(); } void CodeEditorComponent::paint (Graphics& g) { handleUpdateNowIfNeeded(); g.fillAll (findColour (CodeEditorComponent::backgroundColourId)); g.reduceClipRegion (gutter, 0, verticalScrollBar.getX() - gutter, horizontalScrollBar.getY()); g.setFont (font); const int baselineOffset = (int) font.getAscent(); const Colour defaultColour (findColour (CodeEditorComponent::defaultTextColourId)); const Colour highlightColour (findColour (CodeEditorComponent::highlightColourId)); const Rectangle clip (g.getClipBounds()); const int firstLineToDraw = jmax (0, clip.getY() / lineHeight); const int lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1); for (int j = firstLineToDraw; j < lastLineToDraw; ++j) { lines.getUnchecked(j)->draw (*this, g, font, (float) (gutter - xOffset * charWidth), lineHeight * j, baselineOffset, lineHeight, highlightColour); } } void CodeEditorComponent::setScrollbarThickness (const int thickness) { if (scrollbarThickness != thickness) { scrollbarThickness = thickness; resized(); } } void CodeEditorComponent::handleAsyncUpdate() { rebuildLineTokens(); } void CodeEditorComponent::rebuildLineTokens() { cancelPendingUpdate(); const int numNeeded = linesOnScreen + 1; int minLineToRepaint = numNeeded; int maxLineToRepaint = 0; if (numNeeded != lines.size()) { lines.clear(); for (int i = numNeeded; --i >= 0;) lines.add (new CodeEditorLine()); minLineToRepaint = 0; maxLineToRepaint = numNeeded; } jassert (numNeeded == lines.size()); CodeDocument::Iterator source (&document); getIteratorForPosition (CodeDocument::Position (&document, firstLineOnScreen, 0).getPosition(), source); for (int i = 0; i < numNeeded; ++i) { CodeEditorLine* const line = lines.getUnchecked(i); if (line->update (document, firstLineOnScreen + i, source, codeTokeniser, spacesPerTab, selectionStart, selectionEnd)) { minLineToRepaint = jmin (minLineToRepaint, i); maxLineToRepaint = jmax (maxLineToRepaint, i); } } if (minLineToRepaint <= maxLineToRepaint) { repaint (gutter, lineHeight * minLineToRepaint - 1, verticalScrollBar.getX() - gutter, lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2); } } void CodeEditorComponent::updateCaretPosition() { caret->setCaretPosition (getCharacterBounds (getCaretPos())); } void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting) { caretPos = newPos; columnToTryToMaintain = -1; if (highlighting) { if (dragType == notDragging) { if (abs (caretPos.getPosition() - selectionStart.getPosition()) < abs (caretPos.getPosition() - selectionEnd.getPosition())) dragType = draggingSelectionStart; else dragType = draggingSelectionEnd; } if (dragType == draggingSelectionStart) { selectionStart = caretPos; if (selectionEnd.getPosition() < selectionStart.getPosition()) { const CodeDocument::Position temp (selectionStart); selectionStart = selectionEnd; selectionEnd = temp; dragType = draggingSelectionEnd; } } else { selectionEnd = caretPos; if (selectionEnd.getPosition() < selectionStart.getPosition()) { const CodeDocument::Position temp (selectionStart); selectionStart = selectionEnd; selectionEnd = temp; dragType = draggingSelectionStart; } } triggerAsyncUpdate(); } else { deselectAll(); } updateCaretPosition(); scrollToKeepCaretOnScreen(); updateScrollBars(); } void CodeEditorComponent::deselectAll() { if (selectionStart != selectionEnd) triggerAsyncUpdate(); selectionStart = caretPos; selectionEnd = caretPos; } void CodeEditorComponent::updateScrollBars() { verticalScrollBar.setRangeLimits (0, jmax (document.getNumLines(), firstLineOnScreen + linesOnScreen)); verticalScrollBar.setCurrentRange (firstLineOnScreen, linesOnScreen); horizontalScrollBar.setRangeLimits (0, jmax ((double) document.getMaximumLineLength(), xOffset + columnsOnScreen)); horizontalScrollBar.setCurrentRange (xOffset, columnsOnScreen); } void CodeEditorComponent::scrollToLineInternal (int newFirstLineOnScreen) { newFirstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), newFirstLineOnScreen); if (newFirstLineOnScreen != firstLineOnScreen) { firstLineOnScreen = newFirstLineOnScreen; updateCaretPosition(); updateCachedIterators (firstLineOnScreen); triggerAsyncUpdate(); } } void CodeEditorComponent::scrollToColumnInternal (double column) { const double newOffset = jlimit (0.0, document.getMaximumLineLength() + 3.0, column); if (xOffset != newOffset) { xOffset = newOffset; updateCaretPosition(); repaint(); } } void CodeEditorComponent::scrollToLine (int newFirstLineOnScreen) { scrollToLineInternal (newFirstLineOnScreen); updateScrollBars(); } void CodeEditorComponent::scrollToColumn (int newFirstColumnOnScreen) { scrollToColumnInternal (newFirstColumnOnScreen); updateScrollBars(); } void CodeEditorComponent::scrollBy (int deltaLines) { scrollToLine (firstLineOnScreen + deltaLines); } void CodeEditorComponent::scrollToKeepCaretOnScreen() { if (caretPos.getLineNumber() < firstLineOnScreen) scrollBy (caretPos.getLineNumber() - firstLineOnScreen); else if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) scrollBy (caretPos.getLineNumber() - (firstLineOnScreen + linesOnScreen - 1)); const int column = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); if (column >= xOffset + columnsOnScreen - 1) scrollToColumn (column + 1 - columnsOnScreen); else if (column < xOffset) scrollToColumn (column); } const Rectangle CodeEditorComponent::getCharacterBounds (const CodeDocument::Position& pos) const { return Rectangle (roundToInt ((gutter - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth), (pos.getLineNumber() - firstLineOnScreen) * lineHeight, roundToInt (charWidth), lineHeight); } const CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) { const int line = y / lineHeight + firstLineOnScreen; const int column = roundToInt ((x - (gutter - xOffset * charWidth)) / charWidth); const int index = columnToIndex (line, column); return CodeDocument::Position (&document, line, index); } void CodeEditorComponent::insertTextAtCaret (const String& newText) { document.deleteSection (selectionStart, selectionEnd); if (newText.isNotEmpty()) document.insertText (caretPos, newText); scrollToKeepCaretOnScreen(); } void CodeEditorComponent::insertTabAtCaret() { if (CharacterFunctions::isWhitespace (caretPos.getCharacter()) && caretPos.getLineNumber() == caretPos.movedBy (1).getLineNumber()) { moveCaretTo (document.findWordBreakAfter (caretPos), false); } if (useSpacesForTabs) { const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab); insertTextAtCaret (String::repeatedString (" ", spacesNeeded)); } else { insertTextAtCaret ("\t"); } } void CodeEditorComponent::cut() { insertTextAtCaret (String::empty); } bool CodeEditorComponent::copyToClipboard() { newTransaction(); const String selection (document.getTextBetween (selectionStart, selectionEnd)); if (selection.isNotEmpty()) SystemClipboard::copyTextToClipboard (selection); return true; } bool CodeEditorComponent::cutToClipboard() { copyToClipboard(); cut(); newTransaction(); return true; } bool CodeEditorComponent::pasteFromClipboard() { newTransaction(); const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); newTransaction(); return true; } bool CodeEditorComponent::moveCaretLeft (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakBefore (caretPos), selecting); else moveCaretTo (caretPos.movedBy (-1), selecting); return true; } bool CodeEditorComponent::moveCaretRight (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakAfter (caretPos), selecting); else moveCaretTo (caretPos.movedBy (1), selecting); return true; } void CodeEditorComponent::moveLineDelta (const int delta, const bool selecting) { CodeDocument::Position pos (caretPos); const int newLineNum = pos.getLineNumber() + delta; if (columnToTryToMaintain < 0) columnToTryToMaintain = indexToColumn (pos.getLineNumber(), pos.getIndexInLine()); pos.setLineAndIndex (newLineNum, columnToIndex (newLineNum, columnToTryToMaintain)); const int colToMaintain = columnToTryToMaintain; moveCaretTo (pos, selecting); columnToTryToMaintain = colToMaintain; } bool CodeEditorComponent::moveCaretDown (const bool selecting) { newTransaction(); if (caretPos.getLineNumber() == document.getNumLines() - 1) moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), selecting); else moveLineDelta (1, selecting); return true; } bool CodeEditorComponent::moveCaretUp (const bool selecting) { newTransaction(); if (caretPos.getLineNumber() == 0) moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); else moveLineDelta (-1, selecting); return true; } bool CodeEditorComponent::pageDown (const bool selecting) { newTransaction(); scrollBy (jlimit (0, linesOnScreen, 1 + document.getNumLines() - firstLineOnScreen - linesOnScreen)); moveLineDelta (linesOnScreen, selecting); return true; } bool CodeEditorComponent::pageUp (const bool selecting) { newTransaction(); scrollBy (-linesOnScreen); moveLineDelta (-linesOnScreen, selecting); return true; } bool CodeEditorComponent::scrollUp() { newTransaction(); scrollBy (1); if (caretPos.getLineNumber() < firstLineOnScreen) moveLineDelta (1, false); return true; } bool CodeEditorComponent::scrollDown() { newTransaction(); scrollBy (-1); if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) moveLineDelta (-1, false); return true; } bool CodeEditorComponent::moveCaretToTop (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); return true; } namespace CodeEditorHelpers { int findFirstNonWhitespaceChar (const String& line) noexcept { String::CharPointerType t (line.getCharPointer()); int i = 0; while (! t.isEmpty()) { if (! t.isWhitespace()) return i; ++t; ++i; } return 0; } } bool CodeEditorComponent::moveCaretToStartOfLine (const bool selecting) { newTransaction(); int index = CodeEditorHelpers::findFirstNonWhitespaceChar (caretPos.getLineText()); if (index >= caretPos.getIndexInLine() && caretPos.getIndexInLine() > 0) index = 0; moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), index), selecting); return true; } bool CodeEditorComponent::moveCaretToEnd (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), selecting); return true; } bool CodeEditorComponent::moveCaretToEndOfLine (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), std::numeric_limits::max()), selecting); return true; } bool CodeEditorComponent::deleteBackwards (const bool moveInWholeWordSteps) { if (moveInWholeWordSteps) { cut(); // in case something is already highlighted moveCaretTo (document.findWordBreakBefore (caretPos), true); } else { if (selectionStart == selectionEnd) selectionStart.moveBy (-1); } cut(); return true; } bool CodeEditorComponent::deleteForwards (const bool moveInWholeWordSteps) { if (moveInWholeWordSteps) { cut(); // in case something is already highlighted moveCaretTo (document.findWordBreakAfter (caretPos), true); } else { if (selectionStart == selectionEnd) selectionEnd.moveBy (1); else newTransaction(); } cut(); return true; } bool CodeEditorComponent::selectAll() { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), false); moveCaretTo (CodeDocument::Position (&document, 0, 0), true); return true; } bool CodeEditorComponent::undo() { document.undo(); scrollToKeepCaretOnScreen(); return true; } bool CodeEditorComponent::redo() { document.redo(); scrollToKeepCaretOnScreen(); return true; } void CodeEditorComponent::newTransaction() { document.newTransaction(); startTimer (600); } void CodeEditorComponent::timerCallback() { newTransaction(); } const Range CodeEditorComponent::getHighlightedRegion() const { return Range (selectionStart.getPosition(), selectionEnd.getPosition()); } void CodeEditorComponent::setHighlightedRegion (const Range& newRange) { moveCaretTo (CodeDocument::Position (&document, newRange.getStart()), false); moveCaretTo (CodeDocument::Position (&document, newRange.getEnd()), true); } const String CodeEditorComponent::getTextInRange (const Range& range) const { return document.getTextBetween (CodeDocument::Position (&document, range.getStart()), CodeDocument::Position (&document, range.getEnd())); } bool CodeEditorComponent::keyPressed (const KeyPress& key) { if (! TextEditorKeyMapper::invokeKeyFunction (*this, key)) { if (key == KeyPress::tabKey || key.getTextCharacter() == '\t') { insertTabAtCaret(); } else if (key == KeyPress::returnKey) { newTransaction(); insertTextAtCaret (document.getNewLineCharacters()); } else if (key.isKeyCode (KeyPress::escapeKey)) { newTransaction(); } else if (key.getTextCharacter() >= ' ') { insertTextAtCaret (String::charToString (key.getTextCharacter())); } else { return false; } } return true; } void CodeEditorComponent::mouseDown (const MouseEvent& e) { newTransaction(); dragType = notDragging; if (! e.mods.isPopupMenu()) { beginDragAutoRepeat (100); moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown()); } else { /*PopupMenu m; addPopupMenuItems (m, &e); const int result = m.show(); if (result != 0) performPopupMenuAction (result); */ } } void CodeEditorComponent::mouseDrag (const MouseEvent& e) { if (! e.mods.isPopupMenu()) moveCaretTo (getPositionAt (e.x, e.y), true); } void CodeEditorComponent::mouseUp (const MouseEvent&) { newTransaction(); beginDragAutoRepeat (0); dragType = notDragging; } void CodeEditorComponent::mouseDoubleClick (const MouseEvent& e) { CodeDocument::Position tokenStart (getPositionAt (e.x, e.y)); CodeDocument::Position tokenEnd (tokenStart); if (e.getNumberOfClicks() > 2) { tokenStart.setLineAndIndex (tokenStart.getLineNumber(), 0); tokenEnd.setLineAndIndex (tokenStart.getLineNumber() + 1, 0); } else { while (CharacterFunctions::isLetterOrDigit (tokenEnd.getCharacter())) tokenEnd.moveBy (1); tokenStart = tokenEnd; while (tokenStart.getIndexInLine() > 0 && CharacterFunctions::isLetterOrDigit (tokenStart.movedBy (-1).getCharacter())) tokenStart.moveBy (-1); } moveCaretTo (tokenEnd, false); moveCaretTo (tokenStart, true); } void CodeEditorComponent::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { if ((verticalScrollBar.isVisible() && wheelIncrementY != 0) || (horizontalScrollBar.isVisible() && wheelIncrementX != 0)) { verticalScrollBar.mouseWheelMove (e, 0, wheelIncrementY); horizontalScrollBar.mouseWheelMove (e, wheelIncrementX, 0); } else { Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } } void CodeEditorComponent::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) { if (scrollBarThatHasMoved == &verticalScrollBar) scrollToLineInternal ((int) newRangeStart); else scrollToColumnInternal (newRangeStart); } void CodeEditorComponent::focusGained (FocusChangeType) { updateCaretPosition(); } void CodeEditorComponent::focusLost (FocusChangeType) { updateCaretPosition(); } void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) { useSpacesForTabs = insertSpaces; if (spacesPerTab != numSpaces) { spacesPerTab = numSpaces; triggerAsyncUpdate(); } } int CodeEditorComponent::indexToColumn (int lineNum, int index) const noexcept { String::CharPointerType t (document.getLine (lineNum).getCharPointer()); int col = 0; for (int i = 0; i < index; ++i) { if (t.isEmpty()) { jassertfalse; break; } if (t.getAndAdvance() != '\t') ++col; else col += getTabSize() - (col % getTabSize()); } return col; } int CodeEditorComponent::columnToIndex (int lineNum, int column) const noexcept { String::CharPointerType t (document.getLine (lineNum).getCharPointer()); int i = 0, col = 0; while (! t.isEmpty()) { if (t.getAndAdvance() != '\t') ++col; else col += getTabSize() - (col % getTabSize()); if (col > column) break; ++i; } return i; } void CodeEditorComponent::setFont (const Font& newFont) { font = newFont; charWidth = font.getStringWidthFloat ("0"); lineHeight = roundToInt (font.getHeight()); resized(); } void CodeEditorComponent::resetToDefaultColours() { coloursForTokenCategories.clear(); if (codeTokeniser != nullptr) { for (int i = codeTokeniser->getTokenTypes().size(); --i >= 0;) setColourForTokenType (i, codeTokeniser->getDefaultColour (i)); } } void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colour& colour) { jassert (tokenType < 256); while (coloursForTokenCategories.size() < tokenType) coloursForTokenCategories.add (Colours::black); coloursForTokenCategories.set (tokenType, colour); repaint(); } const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const { if (! isPositiveAndBelow (tokenType, coloursForTokenCategories.size())) return findColour (CodeEditorComponent::defaultTextColourId); return coloursForTokenCategories.getReference (tokenType); } void CodeEditorComponent::clearCachedIterators (const int firstLineToBeInvalid) { int i; for (i = cachedIterators.size(); --i >= 0;) if (cachedIterators.getUnchecked (i)->getLine() < firstLineToBeInvalid) break; cachedIterators.removeRange (jmax (0, i - 1), cachedIterators.size()); } void CodeEditorComponent::updateCachedIterators (int maxLineNum) { const int maxNumCachedPositions = 5000; const int linesBetweenCachedSources = jmax (10, document.getNumLines() / maxNumCachedPositions); if (cachedIterators.size() == 0) cachedIterators.add (new CodeDocument::Iterator (&document)); if (codeTokeniser == nullptr) return; for (;;) { CodeDocument::Iterator* last = cachedIterators.getLast(); if (last->getLine() >= maxLineNum) break; CodeDocument::Iterator* t = new CodeDocument::Iterator (*last); cachedIterators.add (t); const int targetLine = last->getLine() + linesBetweenCachedSources; for (;;) { codeTokeniser->readNextToken (*t); if (t->getLine() >= targetLine) break; if (t->isEOF()) return; } } } void CodeEditorComponent::getIteratorForPosition (int position, CodeDocument::Iterator& source) { if (codeTokeniser == nullptr) return; for (int i = cachedIterators.size(); --i >= 0;) { CodeDocument::Iterator* t = cachedIterators.getUnchecked (i); if (t->getPosition() <= position) { source = *t; break; } } while (source.getPosition() < position) { const CodeDocument::Iterator original (source); codeTokeniser->readNextToken (source); if (source.getPosition() > position || source.isEOF()) { source = original; break; } } } END_JUCE_NAMESPACE /*** End of inlined file: juce_CodeEditorComponent.cpp ***/ /*** Start of inlined file: juce_CPlusPlusCodeTokeniser.cpp ***/ BEGIN_JUCE_NAMESPACE CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() { } CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() { } namespace CppTokeniser { bool isIdentifierStart (const juce_wchar c) noexcept { return CharacterFunctions::isLetter (c) || c == '_' || c == '@'; } bool isIdentifierBody (const juce_wchar c) noexcept { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '@'; } bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept { static const char* const keywords2Char[] = { "if", "do", "or", "id", 0 }; static const char* const keywords3Char[] = { "for", "int", "new", "try", "xor", "and", "asm", "not", 0 }; static const char* const keywords4Char[] = { "bool", "void", "this", "true", "long", "else", "char", "enum", "case", "goto", "auto", 0 }; static const char* const keywords5Char[] = { "while", "bitor", "break", "catch", "class", "compl", "const", "false", "float", "short", "throw", "union", "using", "or_eq", 0 }; static const char* const keywords6Char[] = { "return", "struct", "and_eq", "bitand", "delete", "double", "extern", "friend", "inline", "not_eq", "public", "sizeof", "static", "signed", "switch", "typeid", "wchar_t", "xor_eq", 0}; static const char* const keywordsOther[] = { "const_cast", "continue", "default", "explicit", "mutable", "namespace", "operator", "private", "protected", "register", "reinterpret_cast", "static_cast", "template", "typedef", "typename", "unsigned", "virtual", "volatile", "@implementation", "@interface", "@end", "@synthesize", "@dynamic", "@public", "@private", "@property", "@protected", "@class", 0 }; const char* const* k; switch (tokenLength) { case 2: k = keywords2Char; break; case 3: k = keywords3Char; break; case 4: k = keywords4Char; break; case 5: k = keywords5Char; break; case 6: k = keywords6Char; break; default: if (tokenLength < 2 || tokenLength > 16) return false; k = keywordsOther; break; } int i = 0; while (k[i] != 0) { if (token.compare (CharPointer_ASCII (k[i])) == 0) return true; ++i; } return false; } int parseIdentifier (CodeDocument::Iterator& source) noexcept { int tokenLength = 0; String::CharPointerType::CharType possibleIdentifier [100]; String::CharPointerType possible (possibleIdentifier); while (isIdentifierBody (source.peekNextChar())) { const juce_wchar c = source.nextChar(); if (tokenLength < 20) possible.write (c); ++tokenLength; } if (tokenLength > 1 && tokenLength <= 16) { possible.writeNull(); if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength)) return CPlusPlusCodeTokeniser::tokenType_builtInKeyword; } return CPlusPlusCodeTokeniser::tokenType_identifier; } bool skipNumberSuffix (CodeDocument::Iterator& source) { const juce_wchar c = source.peekNextChar(); if (c == 'l' || c == 'L' || c == 'u' || c == 'U') source.skip(); if (CharacterFunctions::isLetterOrDigit (source.peekNextChar())) return false; return true; } bool isHexDigit (const juce_wchar c) noexcept { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } bool parseHexLiteral (CodeDocument::Iterator& source) noexcept { if (source.nextChar() != '0') return false; juce_wchar c = source.nextChar(); if (c != 'x' && c != 'X') return false; int numDigits = 0; while (isHexDigit (source.peekNextChar())) { ++numDigits; source.skip(); } if (numDigits == 0) return false; return skipNumberSuffix (source); } bool isOctalDigit (const juce_wchar c) noexcept { return c >= '0' && c <= '7'; } bool parseOctalLiteral (CodeDocument::Iterator& source) noexcept { if (source.nextChar() != '0') return false; if (! isOctalDigit (source.nextChar())) return false; while (isOctalDigit (source.peekNextChar())) source.skip(); return skipNumberSuffix (source); } bool isDecimalDigit (const juce_wchar c) noexcept { return c >= '0' && c <= '9'; } bool parseDecimalLiteral (CodeDocument::Iterator& source) noexcept { int numChars = 0; while (isDecimalDigit (source.peekNextChar())) { ++numChars; source.skip(); } if (numChars == 0) return false; return skipNumberSuffix (source); } bool parseFloatLiteral (CodeDocument::Iterator& source) noexcept { int numDigits = 0; while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numDigits; } const bool hasPoint = (source.peekNextChar() == '.'); if (hasPoint) { source.skip(); while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numDigits; } } if (numDigits == 0) return false; juce_wchar c = source.peekNextChar(); const bool hasExponent = (c == 'e' || c == 'E'); if (hasExponent) { source.skip(); c = source.peekNextChar(); if (c == '+' || c == '-') source.skip(); int numExpDigits = 0; while (isDecimalDigit (source.peekNextChar())) { source.skip(); ++numExpDigits; } if (numExpDigits == 0) return false; } c = source.peekNextChar(); if (c == 'f' || c == 'F') source.skip(); else if (! (hasExponent || hasPoint)) return false; return true; } int parseNumber (CodeDocument::Iterator& source) { const CodeDocument::Iterator original (source); if (parseFloatLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_floatLiteral; source = original; if (parseHexLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; if (parseOctalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; if (parseDecimalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integerLiteral; source = original; source.skip(); return CPlusPlusCodeTokeniser::tokenType_error; } void skipQuotedString (CodeDocument::Iterator& source) noexcept { const juce_wchar quote = source.nextChar(); for (;;) { const juce_wchar c = source.nextChar(); if (c == quote || c == 0) break; if (c == '\\') source.skip(); } } void skipComment (CodeDocument::Iterator& source) noexcept { bool lastWasStar = false; for (;;) { const juce_wchar c = source.nextChar(); if (c == 0 || (c == '/' && lastWasStar)) break; lastWasStar = (c == '*'); } } } int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source) { int result = tokenType_error; source.skipWhitespace(); juce_wchar firstChar = source.peekNextChar(); switch (firstChar) { case 0: source.skip(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result = CppTokeniser::parseNumber (source); break; case '.': result = CppTokeniser::parseNumber (source); if (result == tokenType_error) result = tokenType_punctuation; break; case ',': case ';': case ':': source.skip(); result = tokenType_punctuation; break; case '(': case ')': case '{': case '}': case '[': case ']': source.skip(); result = tokenType_bracket; break; case '"': case '\'': CppTokeniser::skipQuotedString (source); result = tokenType_stringLiteral; break; case '+': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '+') source.skip(); else if (source.peekNextChar() == '=') source.skip(); break; case '-': source.skip(); result = CppTokeniser::parseNumber (source); if (result == tokenType_error) { result = tokenType_operator; if (source.peekNextChar() == '-') source.skip(); else if (source.peekNextChar() == '=') source.skip(); } break; case '*': case '%': case '=': case '!': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '=') source.skip(); break; case '/': result = tokenType_operator; source.skip(); if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '/') { result = tokenType_comment; source.skipToEndOfLine(); } else if (source.peekNextChar() == '*') { source.skip(); result = tokenType_comment; CppTokeniser::skipComment (source); } break; case '?': case '~': source.skip(); result = tokenType_operator; break; case '<': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '<') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '>': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '<') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '|': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '|') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '&': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '&') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '^': source.skip(); result = tokenType_operator; if (source.peekNextChar() == '=') { source.skip(); } else if (source.peekNextChar() == '^') { source.skip(); if (source.peekNextChar() == '=') source.skip(); } break; case '#': result = tokenType_preprocessor; source.skipToEndOfLine(); break; default: if (CppTokeniser::isIdentifierStart (firstChar)) result = CppTokeniser::parseIdentifier (source); else source.skip(); break; } return result; } const StringArray CPlusPlusCodeTokeniser::getTokenTypes() { const char* const types[] = { "Error", "Comment", "C++ keyword", "Identifier", "Integer literal", "Float literal", "String literal", "Operator", "Bracket", "Punctuation", "Preprocessor line", 0 }; return StringArray (types); } const Colour CPlusPlusCodeTokeniser::getDefaultColour (const int tokenType) { const uint32 colours[] = { 0xffcc0000, // error 0xff00aa00, // comment 0xff0000cc, // keyword 0xff000000, // identifier 0xff880000, // int literal 0xff885500, // float literal 0xff990099, // string literal 0xff225500, // operator 0xff000055, // bracket 0xff004400, // punctuation 0xff660000 // preprocessor }; if (tokenType >= 0 && tokenType < numElementsInArray (colours)) return Colour (colours [tokenType]); return Colours::black; } bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) noexcept { return CppTokeniser::isReservedKeyword (token.getCharPointer(), token.length()); } END_JUCE_NAMESPACE /*** End of inlined file: juce_CPlusPlusCodeTokeniser.cpp ***/ /*** Start of inlined file: juce_ComboBox.cpp ***/ BEGIN_JUCE_NAMESPACE ComboBox::ItemInfo::ItemInfo (const String& name_, int itemId_, bool isEnabled_, bool isHeading_) : name (name_), itemId (itemId_), isEnabled (isEnabled_), isHeading (isHeading_) { } bool ComboBox::ItemInfo::isSeparator() const noexcept { return name.isEmpty(); } bool ComboBox::ItemInfo::isRealItem() const noexcept { return ! (isHeading || name.isEmpty()); } ComboBox::ComboBox (const String& name) : Component (name), lastCurrentId (0), isButtonDown (false), separatorPending (false), menuActive (false), noChoicesMessage (TRANS("(no choices)")) { setRepaintsOnMouseActivity (true); ComboBox::lookAndFeelChanged(); currentId.addListener (this); } ComboBox::~ComboBox() { currentId.removeListener (this); if (menuActive) PopupMenu::dismissAllActiveMenus(); label = nullptr; } void ComboBox::setEditableText (const bool isEditable) { if (label->isEditableOnSingleClick() != isEditable || label->isEditableOnDoubleClick() != isEditable) { label->setEditable (isEditable, isEditable, false); setWantsKeyboardFocus (! isEditable); resized(); } } bool ComboBox::isTextEditable() const noexcept { return label->isEditable(); } void ComboBox::setJustificationType (const Justification& justification) { label->setJustificationType (justification); } const Justification ComboBox::getJustificationType() const noexcept { return label->getJustificationType(); } void ComboBox::setTooltip (const String& newTooltip) { SettableTooltipClient::setTooltip (newTooltip); label->setTooltip (newTooltip); } void ComboBox::addItem (const String& newItemText, const int newItemId) { // you can't add empty strings to the list.. jassert (newItemText.isNotEmpty()); // IDs must be non-zero, as zero is used to indicate a lack of selecion. jassert (newItemId != 0); // you shouldn't use duplicate item IDs! jassert (getItemForId (newItemId) == nullptr); if (newItemText.isNotEmpty() && newItemId != 0) { if (separatorPending) { separatorPending = false; items.add (new ItemInfo (String::empty, 0, false, false)); } items.add (new ItemInfo (newItemText, newItemId, true, false)); } } void ComboBox::addSeparator() { separatorPending = (items.size() > 0); } void ComboBox::addSectionHeading (const String& headingName) { // you can't add empty strings to the list.. jassert (headingName.isNotEmpty()); if (headingName.isNotEmpty()) { if (separatorPending) { separatorPending = false; items.add (new ItemInfo (String::empty, 0, false, false)); } items.add (new ItemInfo (headingName, 0, true, true)); } } void ComboBox::setItemEnabled (const int itemId, const bool shouldBeEnabled) { ItemInfo* const item = getItemForId (itemId); if (item != nullptr) item->isEnabled = shouldBeEnabled; } bool ComboBox::isItemEnabled (int itemId) const noexcept { const ItemInfo* const item = getItemForId (itemId); return item != nullptr && item->isEnabled; } void ComboBox::changeItemText (const int itemId, const String& newText) { ItemInfo* const item = getItemForId (itemId); jassert (item != nullptr); if (item != nullptr) item->name = newText; } void ComboBox::clear (const bool dontSendChangeMessage) { items.clear(); separatorPending = false; if (! label->isEditable()) setSelectedItemIndex (-1, dontSendChangeMessage); } ComboBox::ItemInfo* ComboBox::getItemForId (const int itemId) const noexcept { if (itemId != 0) { for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->itemId == itemId) return items.getUnchecked(i); } return nullptr; } ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const noexcept { for (int n = 0, i = 0; i < items.size(); ++i) { ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem()) if (n++ == index) return item; } return nullptr; } int ComboBox::getNumItems() const noexcept { int n = 0; for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->isRealItem()) ++n; return n; } String ComboBox::getItemText (const int index) const { const ItemInfo* const item = getItemForIndex (index); return item != nullptr ? item->name : String::empty; } int ComboBox::getItemId (const int index) const noexcept { const ItemInfo* const item = getItemForIndex (index); return item != nullptr ? item->itemId : 0; } int ComboBox::indexOfItemId (const int itemId) const noexcept { for (int n = 0, i = 0; i < items.size(); ++i) { const ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem()) { if (item->itemId == itemId) return n; ++n; } } return -1; } int ComboBox::getSelectedItemIndex() const { int index = indexOfItemId (currentId.getValue()); if (getText() != getItemText (index)) index = -1; return index; } void ComboBox::setSelectedItemIndex (const int index, const bool dontSendChangeMessage) { setSelectedId (getItemId (index), dontSendChangeMessage); } int ComboBox::getSelectedId() const noexcept { const ItemInfo* const item = getItemForId (currentId.getValue()); return (item != nullptr && getText() == item->name) ? item->itemId : 0; } void ComboBox::setSelectedId (const int newItemId, const bool dontSendChangeMessage) { const ItemInfo* const item = getItemForId (newItemId); const String newItemText (item != nullptr ? item->name : String::empty); if (lastCurrentId != newItemId || label->getText() != newItemText) { if (! dontSendChangeMessage) triggerAsyncUpdate(); label->setText (newItemText, false); lastCurrentId = newItemId; currentId = newItemId; repaint(); // for the benefit of the 'none selected' text } } bool ComboBox::selectIfEnabled (const int index) { const ItemInfo* const item = getItemForIndex (index); if (item != nullptr && item->isEnabled) { setSelectedItemIndex (index); return true; } return false; } void ComboBox::valueChanged (Value&) { if (lastCurrentId != (int) currentId.getValue()) setSelectedId (currentId.getValue(), false); } String ComboBox::getText() const { return label->getText(); } void ComboBox::setText (const String& newText, const bool dontSendChangeMessage) { for (int i = items.size(); --i >= 0;) { const ItemInfo* const item = items.getUnchecked(i); if (item->isRealItem() && item->name == newText) { setSelectedId (item->itemId, dontSendChangeMessage); return; } } lastCurrentId = 0; currentId = 0; if (label->getText() != newText) { label->setText (newText, false); if (! dontSendChangeMessage) triggerAsyncUpdate(); } repaint(); } void ComboBox::showEditor() { jassert (isTextEditable()); // you probably shouldn't do this to a non-editable combo box? label->showEditor(); } void ComboBox::setTextWhenNothingSelected (const String& newMessage) { if (textWhenNothingSelected != newMessage) { textWhenNothingSelected = newMessage; repaint(); } } String ComboBox::getTextWhenNothingSelected() const { return textWhenNothingSelected; } void ComboBox::setTextWhenNoChoicesAvailable (const String& newMessage) { noChoicesMessage = newMessage; } String ComboBox::getTextWhenNoChoicesAvailable() const { return noChoicesMessage; } void ComboBox::paint (Graphics& g) { getLookAndFeel().drawComboBox (g, getWidth(), getHeight(), isButtonDown, label->getRight(), 0, getWidth() - label->getRight(), getHeight(), *this); if (textWhenNothingSelected.isNotEmpty() && label->getText().isEmpty() && ! label->isBeingEdited()) { g.setColour (findColour (textColourId).withMultipliedAlpha (0.5f)); g.setFont (label->getFont()); g.drawFittedText (textWhenNothingSelected, label->getX() + 2, label->getY() + 1, label->getWidth() - 4, label->getHeight() - 2, label->getJustificationType(), jmax (1, (int) (label->getHeight() / label->getFont().getHeight()))); } } void ComboBox::resized() { if (getHeight() > 0 && getWidth() > 0) getLookAndFeel().positionComboBoxText (*this, *label); } void ComboBox::enablementChanged() { repaint(); } void ComboBox::lookAndFeelChanged() { repaint(); { ScopedPointer