/* ============================================================================== 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 or JUCE_LINUX. - 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 (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 #ifdef _DEBUG #define JUCE_DEBUG 1 #endif // Allow override for big-endian Linux platforms #ifndef JUCE_BIG_ENDIAN #define JUCE_LITTLE_ENDIAN 1 #endif #if defined (__LP64__) || defined (_LP64) #define JUCE_64BIT 1 #else #define JUCE_32BIT 1 #endif #define JUCE_INTEL 1 #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_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_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. */ #ifndef JUCE_OPENGL #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 #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 #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) Rebuild the whole of JUCE, setting the global definition JUCE_ASIO (you can un-comment the "#define JUCE_ASIO" line in juce_Config.h if you prefer). Make sure that your header search path will find the iasiodrv.h file that comes with the SDK. (Only about 2-3 of the SDK header files are actually needed - so to simplify things, you could just copy these into your JUCE directory). If you're compiling and you get an error here because you don't have the ASIO SDK installed, you can disable ASIO support by commenting-out the "#define JUCE_ASIO" line in juce_Config.h, and rebuild your Juce library. */ #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 #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_MSVC #pragma warning (pop) #endif #if JUCE_DIRECT2D && JUCE_BUILD_NATIVE #include #include #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 /* 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_IPHONE /*** Start of inlined file: juce_mac_NativeIncludes.h ***/ #ifndef __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ #define __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ /* This file wraps together all the mac-specific code, so that we can include all the native headers 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. */ #define USE_COREGRAPHICS_RENDERING 1 #if JUCE_IOS #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 #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 ***/ #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() != 0) 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 (Component&) { 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; 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 != 0); if (in != 0) { in->setPosition (fileSize - maxFileSizeBytes); String content; { MemoryBlock contentToSave; contentToSave.setSize (maxFileSizeBytes + 4); contentToSave.fillWith (0); in->read (contentToSave.getData(), maxFileSizeBytes); in = 0; 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 = 0; 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 != 0) currentLogger->logMessage (message); else outputDebugString (message); } #if JUCE_LOG_ASSERTIONS void JUCE_API juce_LogAssertion (const char* filename, const int lineNum) throw() { 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) throw() : seed (seedValue) { } Random::~Random() throw() { } void Random::setSeed (const int64 newSeed) throw() { seed = newSeed; } void Random::combineSeed (const int64 seedValue) throw() { seed ^= nextInt64() ^ seedValue; } void Random::setSeedRandomly() { combineSeed ((int64) (pointer_sized_int) this); combineSeed (Time::getMillisecondCounter()); combineSeed (Time::getHighResolutionTicks()); combineSeed (Time::getHighResolutionTicksPerSecond()); combineSeed (Time::currentTimeMillis()); } int Random::nextInt() throw() { seed = (seed * literal64bit (0x5deece66d) + 11) & literal64bit (0xffffffffffff); return (int) (seed >> 16); } int Random::nextInt (const int maxValue) throw() { jassert (maxValue > 0); return (nextInt() & 0x7fffffff) % maxValue; } int64 Random::nextInt64() throw() { return (((int64) nextInt()) << 32) | (int64) (uint64) (uint32) nextInt(); } bool Random::nextBool() throw() { return (nextInt() & 0x80000000) != 0; } float Random::nextFloat() throw() { return static_cast (nextInt()) / (float) 0xffffffff; } double Random::nextDouble() throw() { return static_cast (nextInt()) / (double) 0xffffffff; } const 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()); } Random& Random::getSystemRandom() throw() { static Random sysRand (1); return sysRand; } 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_) throw() : seconds (seconds_) { } RelativeTime::RelativeTime (const RelativeTime& other) throw() : seconds (other.seconds) { } RelativeTime::~RelativeTime() throw() { } const RelativeTime RelativeTime::milliseconds (const int milliseconds) throw() { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::milliseconds (const int64 milliseconds) throw() { return RelativeTime (milliseconds * 0.001); } const RelativeTime RelativeTime::minutes (const double numberOfMinutes) throw() { return RelativeTime (numberOfMinutes * 60.0); } const RelativeTime RelativeTime::hours (const double numberOfHours) throw() { return RelativeTime (numberOfHours * (60.0 * 60.0)); } const RelativeTime RelativeTime::days (const double numberOfDays) throw() { return RelativeTime (numberOfDays * (60.0 * 60.0 * 24.0)); } const RelativeTime RelativeTime::weeks (const double numberOfWeeks) throw() { return RelativeTime (numberOfWeeks * (60.0 * 60.0 * 24.0 * 7.0)); } int64 RelativeTime::inMilliseconds() const throw() { return (int64) (seconds * 1000.0); } double RelativeTime::inMinutes() const throw() { return seconds / 60.0; } double RelativeTime::inHours() const throw() { return seconds / (60.0 * 60.0); } double RelativeTime::inDays() const throw() { return seconds / (60.0 * 60.0 * 24.0); } double RelativeTime::inWeeks() const throw() { return seconds / (60.0 * 60.0 * 24.0 * 7.0); } const String RelativeTime::getDescription (const String& returnValueForZeroTime) const { if (seconds < 0.001 && seconds > -0.001) return returnValueForZeroTime; String result; result.preallocateStorage (16); 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) throw() { seconds = other.seconds; return *this; } const RelativeTime& RelativeTime::operator+= (const RelativeTime& timeToAdd) throw() { seconds += timeToAdd.seconds; return *this; } const RelativeTime& RelativeTime::operator-= (const RelativeTime& timeToSubtract) throw() { seconds -= timeToSubtract.seconds; return *this; } const RelativeTime& RelativeTime::operator+= (const double secondsToAdd) throw() { seconds += secondsToAdd; return *this; } const RelativeTime& RelativeTime::operator-= (const double secondsToSubtract) throw() { seconds -= secondsToSubtract; return *this; } bool operator== (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() == t2.inSeconds(); } bool operator!= (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() != t2.inSeconds(); } bool operator> (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() > t2.inSeconds(); } bool operator< (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() < t2.inSeconds(); } bool operator>= (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() >= t2.inSeconds(); } bool operator<= (const RelativeTime& t1, const RelativeTime& t2) throw() { return t1.inSeconds() <= t2.inSeconds(); } const RelativeTime operator+ (const RelativeTime& t1, const RelativeTime& t2) throw() { RelativeTime t (t1); return t += t2; } const RelativeTime operator- (const RelativeTime& t1, const RelativeTime& t2) throw() { 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 SystemStats::CPUFlags SystemStats::cpuFlags; const String SystemStats::getJUCEVersion() { return "JUCE v" + String (JUCE_MAJOR_VERSION) + "." + String (JUCE_MINOR_VERSION) + "." + String (JUCE_BUILDNUMBER); } #ifdef JUCE_DLL void* juce_Malloc (int size) { return malloc (size); } void* juce_Calloc (int size) { return calloc (1, size); } void* juce_Realloc (void* block, int size) { return realloc (block, size); } void juce_Free (void* block) { free (block); } #if JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS void* juce_DebugMalloc (int size, const char* file, int line) { return _malloc_dbg (size, _NORMAL_BLOCK, file, line); } void* juce_DebugCalloc (int size, const char* file, int line) { return _calloc_dbg (1, size, _NORMAL_BLOCK, file, line); } void* juce_DebugRealloc (void* block, int size, const char* file, int line) { return _realloc_dbg (block, size, _NORMAL_BLOCK, file, line); } void juce_DebugFree (void* block) { _free_dbg (block, _NORMAL_BLOCK); } #endif #endif END_JUCE_NAMESPACE /*** End of inlined file: juce_SystemStats.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 { static struct tm millisToLocal (const int64 millis) throw() { 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 zeromem (&result, sizeof (result)); #else result = *localtime (&now); #endif #else // more thread-safe localtime_r (&now, &result); #endif } return result; } static int extendedModulo (const int64 value, const int modulo) throw() { return (int) (value >= 0 ? (value % modulo) : (value - ((value / modulo) + 1) * modulo)); } static uint32 lastMSCounterValue = 0; } Time::Time() throw() : millisSinceEpoch (0) { } Time::Time (const Time& other) throw() : millisSinceEpoch (other.millisSinceEpoch) { } Time::Time (const int64 ms) throw() : 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) throw() { 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() throw() { } Time& Time::operator= (const Time& other) throw() { millisSinceEpoch = other.millisSinceEpoch; return *this; } int64 Time::currentTimeMillis() throw() { 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() throw(); uint32 Time::getMillisecondCounter() throw() { 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() throw() { jassert (TimeHelpers::lastMSCounterValue != 0); return TimeHelpers::lastMSCounterValue; } void Time::waitForMillisecondCounter (const uint32 targetTime) throw() { 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) throw() { return ticks / (double) getHighResolutionTicksPerSecond(); } int64 Time::secondsToHighResolutionTicks (const double seconds) throw() { return (int64) (seconds * (double) getHighResolutionTicksPerSecond()); } const Time JUCE_CALLTYPE Time::getCurrentTime() throw() { return Time (currentTimeMillis()); } const String Time::toString (const bool includeDate, const bool includeTime, const bool includeSeconds, const bool use24HourClock) const throw() { 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(); } const String Time::formatted (const String& format) const { String buffer; int bufferSize = 128; buffer.preallocateStorage (bufferSize); struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); while (CharacterFunctions::ftime (static_cast (buffer), bufferSize, format, &t) <= 0) { bufferSize += 128; buffer.preallocateStorage (bufferSize); } return buffer; } int Time::getYear() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_year + 1900; } int Time::getMonth() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mon; } int Time::getDayOfMonth() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mday; } int Time::getDayOfWeek() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_wday; } int Time::getHours() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_hour; } int Time::getHoursInAmPmFormat() const throw() { const int hours = getHours(); if (hours == 0) return 12; else if (hours <= 12) return hours; else return hours - 12; } bool Time::isAfternoon() const throw() { return getHours() >= 12; } int Time::getMinutes() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_min; } int Time::getSeconds() const throw() { return TimeHelpers::extendedModulo (millisSinceEpoch / 1000, 60); } int Time::getMilliseconds() const throw() { return TimeHelpers::extendedModulo (millisSinceEpoch, 1000); } bool Time::isDaylightSavingTime() const throw() { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_isdst != 0; } const String Time::getTimeZone() const throw() { String zone[2]; #if JUCE_WINDOWS _tzset(); #ifdef USE_NEW_SECURE_TIME_FNS { char name [128]; size_t length; for (int i = 0; i < 2; ++i) { zeromem (name, sizeof (name)); _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); } const String Time::getMonthName (const bool threeLetterVersion) const { return getMonthName (getMonth(), threeLetterVersion); } const String Time::getWeekdayName (const bool threeLetterVersion) const { return getWeekdayName (getDayOfWeek(), threeLetterVersion); } const 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]); } const 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; } const Time operator+ (const Time& time, const RelativeTime& delta) { Time t (time); return t += delta; } const Time operator- (const Time& time, const RelativeTime& delta) { Time t (time); return t -= delta; } const 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_WINDOWS extern void juce_shutdownWin32Sockets(); // (defined in the sockets code) #endif #if JUCE_DEBUG extern void juce_CheckForDanglingStreams(); // (in juce_OutputStream.cpp) #endif static bool juceInitialisedNonGUI = false; JUCE_API void JUCE_CALLTYPE initialiseJuce_NonGUI() { if (! juceInitialisedNonGUI) { juceInitialisedNonGUI = true; JUCE_AUTORELEASEPOOL DBG (SystemStats::getJUCEVersion()); SystemStats::initialiseStats(); Random::getSystemRandom().setSeedRandomly(); // (mustn't call this before initialiseStats() because it relies on the time being set up) } // 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); } JUCE_API void JUCE_CALLTYPE shutdownJuce_NonGUI() { if (juceInitialisedNonGUI) { juceInitialisedNonGUI = false; JUCE_AUTORELEASEPOOL LocalisedStrings::setCurrentMappings (0); Thread::stopAllThreads (3000); #if JUCE_WINDOWS juce_shutdownWin32Sockets(); #endif #if JUCE_DEBUG juce_CheckForDanglingStreams(); #endif } } #if ! JUCE_ONLY_BUILD_CORE_LIBRARY static bool juceInitialisedGUI = false; JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() { if (! juceInitialisedGUI) { juceInitialisedGUI = true; JUCE_AUTORELEASEPOOL initialiseJuce_NonGUI(); MessageManager::getInstance(); LookAndFeel::setDefaultLookAndFeel (0); #if JUCE_DEBUG try // This section is just a safety-net for catching builds without RTTI enabled.. { MemoryOutputStream mo; OutputStream* o = &mo; // Got an exception here? Then TURN ON RTTI in your compiler settings!! o = dynamic_cast (o); jassert (o != 0); } catch (...) { // Ended up here? If so, TURN ON RTTI in your compiler settings!! jassertfalse; } #endif } } JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI() { if (juceInitialisedGUI) { juceInitialisedGUI = false; JUCE_AUTORELEASEPOOL DeletedAtShutdown::deleteAll(); LookAndFeel::clearDefaultLookAndFeel(); delete MessageManager::getInstance(); shutdownJuce_NonGUI(); } } #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 types"); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testFloat (*this); #if ! JUCE_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms AtomicTester ::testInteger (*this); AtomicTester ::testInteger (*this); AtomicTester ::testFloat (*this); #endif } template class AtomicTester { public: AtomicTester() {} static void testInteger (UnitTest& test) { Atomic a, b; a.set ((Type) 10); a += (Type) 15; a.memoryBarrier(); a -= (Type) 5; ++a; ++a; --a; 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) throw() : bufferSize (capacity) { jassert (bufferSize > 0); } AbstractFifo::~AbstractFifo() {} int AbstractFifo::getTotalSize() const throw() { return bufferSize; } int AbstractFifo::getFreeSpace() const throw() { return bufferSize - getNumReady(); } int AbstractFifo::getNumReady() const throw() { return validEnd.get() - validStart.get(); } void AbstractFifo::reset() throw() { validEnd = 0; validStart = 0; } void AbstractFifo::setTotalSize (int newSize) throw() { jassert (newSize > 0); reset(); bufferSize = newSize; } void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const throw() { const int vs = validStart.get(); const int ve = validEnd.get(); const int freeSpace = bufferSize - (ve - vs); numToWrite = jmin (numToWrite, freeSpace); if (numToWrite <= 0) { startIndex1 = 0; startIndex2 = 0; blockSize1 = 0; blockSize2 = 0; } else { startIndex1 = (int) (ve % bufferSize); startIndex2 = 0; blockSize1 = jmin (bufferSize - startIndex1, numToWrite); numToWrite -= blockSize1; blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, (int) (vs % bufferSize)); } } void AbstractFifo::finishedWrite (int numWritten) throw() { jassert (numWritten >= 0 && numWritten < bufferSize); validEnd += numWritten; } void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const throw() { const int vs = validStart.get(); const int ve = validEnd.get(); const int numReady = ve - vs; numWanted = jmin (numWanted, numReady); if (numWanted <= 0) { startIndex1 = 0; startIndex2 = 0; blockSize1 = 0; blockSize2 = 0; } else { startIndex1 = (int) (vs % bufferSize); startIndex2 = 0; blockSize1 = jmin (bufferSize - startIndex1, numWanted); numWanted -= blockSize1; blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, (int) (ve % bufferSize)); } } void AbstractFifo::finishedRead (int numRead) throw() { jassert (numRead >= 0 && numRead < bufferSize); validStart += numRead; } 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) throw() { values.swapWith (other.values); swapVariables (numValues, other.numValues); swapVariables (highestBit, other.highestBit); swapVariables (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 throw() { return bit <= highestBit && bit >= 0 && ((values [bitToIndex (bit)] & bitToMask (bit)) != 0); } int BigInteger::toInteger() const throw() { const int n = (int) (values[0] & 0x7fffffff); return negative ? -n : n; } const 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 throw() { 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 { zeromem (values, sizeof (uint32) * (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) throw() { 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 throw() { return getHighestBit() < 0; } bool BigInteger::isOne() const throw() { return getHighestBit() == 0 && ! negative; } bool BigInteger::isNegative() const throw() { return negative && ! isZero(); } void BigInteger::setNegative (const bool neg) throw() { negative = neg; } void BigInteger::negate() throw() { negative = (! negative) && ! isZero(); } #if JUCE_USE_INTRINSICS #pragma intrinsic (_BitScanReverse) #endif namespace BitFunctions { inline int countBitsInInt32 (uint32 n) throw() { 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) throw() { 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 throw() { int total = 0; for (int i = bitToIndex (highestBit) + 1; --i >= 0;) total += BitFunctions::countBitsInInt32 (values[i]); return total; } int BigInteger::getHighestBit() const throw() { 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 throw() { for (; i <= highestBit; ++i) if ((values [bitToIndex (i)] & bitToMask (i)) != 0) return i; return -1; } int BigInteger::findNextClearBit (int i) const throw() { 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<<= (int numBitsToShift) { shiftBits (numBitsToShift, 0); return *this; } BigInteger& BigInteger::operator>>= (int numBitsToShift) { return operator<<= (-numBitsToShift); } BigInteger& BigInteger::operator++() { return operator+= (1); } BigInteger& BigInteger::operator--() { return operator-= (1); } const BigInteger BigInteger::operator++ (int) { const BigInteger old (*this); operator+= (1); return old; } const BigInteger BigInteger::operator-- (int) { const BigInteger old (*this); operator-= (1); return old; } const BigInteger BigInteger::operator+ (const BigInteger& other) const { BigInteger b (*this); return b += other; } const BigInteger BigInteger::operator- (const BigInteger& other) const { BigInteger b (*this); return b -= other; } const BigInteger BigInteger::operator* (const BigInteger& other) const { BigInteger b (*this); return b *= other; } const BigInteger BigInteger::operator/ (const BigInteger& other) const { BigInteger b (*this); return b /= other; } const BigInteger BigInteger::operator| (const BigInteger& other) const { BigInteger b (*this); return b |= other; } const BigInteger BigInteger::operator& (const BigInteger& other) const { BigInteger b (*this); return b &= other; } const BigInteger BigInteger::operator^ (const BigInteger& other) const { BigInteger b (*this); return b ^= other; } const BigInteger BigInteger::operator% (const BigInteger& other) const { BigInteger b (*this); return b %= other; } const BigInteger BigInteger::operator<< (const int numBits) const { BigInteger b (*this); return b <<= numBits; } const BigInteger BigInteger::operator>> (const int numBits) const { BigInteger b (*this); return b >>= numBits; } const BigInteger BigInteger::operator-() const { BigInteger b (*this); b.negate(); return b; } int BigInteger::compare (const BigInteger& other) const throw() { 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 throw() { 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 throw() { return compare (other) == 0; } bool BigInteger::operator!= (const BigInteger& other) const throw() { return compare (other) != 0; } bool BigInteger::operator< (const BigInteger& other) const throw() { return compare (other) < 0; } bool BigInteger::operator<= (const BigInteger& other) const throw() { return compare (other) <= 0; } bool BigInteger::operator> (const BigInteger& other) const throw() { return compare (other) > 0; } bool BigInteger::operator>= (const BigInteger& other) const throw() { return compare (other) >= 0; } void BigInteger::shiftBits (int bits, const int startBit) { if (highestBit < 0) return; if (startBit > 0) { if (bits < 0) { // right shift for (int i = startBit; i <= highestBit; ++i) setBit (i, operator[] (i - bits)); highestBit = getHighestBit(); } else if (bits > 0) { // left shift for (int i = highestBit + 1; --i >= startBit;) setBit (i + bits, operator[] (i)); while (--bits >= 0) clearBit (bits + startBit); } } else { if (bits < 0) { // right shift bits = -bits; 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(); } } else if (bits > 0) { // left shift 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(); } } } const BigInteger BigInteger::simpleGCD (BigInteger* m, BigInteger* n) { while (! m->isZero()) { if (n->compareAbsolute (*m) > 0) swapVariables (m, n); *m -= *n; } return *n; } const 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); } const 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(); const juce_wchar* t = text; if (base == 2 || base == 8 || base == 16) { const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); for (;;) { const juce_wchar c = *t++; 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++; if (c >= '0' && c <= '9') { operator*= (ten); operator+= ((int) (c - '0')); } else if (c == 0) { break; } } } setNegative (text.trimStart().startsWithChar ('-')); } const 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() throw() : 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 != 0); 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 != 0); // non-zero size, but a zero pointer passed-in? data.malloc (size); if (dataToInitialiseFrom != 0) memcpy (data, dataToInitialiseFrom, size); } } MemoryBlock::~MemoryBlock() throw() { jassert (size >= 0); // should never happen jassert (size == 0 || data != 0); // 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 throw() { return matches (other.data, other.size); } bool MemoryBlock::operator!= (const MemoryBlock& other) const throw() { return ! operator== (other); } bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const throw() { 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 != 0) { 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) throw() { swapVariables (size, other.size); data.swapWith (other.data); } void MemoryBlock::fillWith (const uint8 value) throw() { 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) throw() { 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 throw() { 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 < 0) { numBytesToRemove += startByte; startByte = 0; } 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 throw() { 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) throw() { 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; int i = 0; for (;;) { int byte = 0; for (int loop = 2; --loop >= 0;) { byte <<= 4; for (;;) { const juce_wchar c = hex [i++]; 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.preallocateStorage (initialLen + 2 + numChars); juce_wchar* d = destString; d += initialLen; *d++ = '.'; for (size_t i = 0; i < numChars; ++i) *d++ = encodingTable [getBitRange (i * 6, 6)]; *d++ = 0; 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; const juce_wchar* srcChars = s; srcChars += startPos; int pos = 0; for (int i = 0; i < numChars; ++i) { const char c = (char) srcChars[i]; 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 (0), 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(); } } const String PropertySet::getValue (const String& keyName, const String& defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index]; return fallbackProperties != 0 ? fallbackProperties->getValue (keyName, defaultValue) : defaultValue; } int PropertySet::getIntValue (const String& keyName, const int defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue(); return fallbackProperties != 0 ? fallbackProperties->getIntValue (keyName, defaultValue) : defaultValue; } double PropertySet::getDoubleValue (const String& keyName, const double defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues()[index].getDoubleValue(); return fallbackProperties != 0 ? fallbackProperties->getDoubleValue (keyName, defaultValue) : defaultValue; } bool PropertySet::getBoolValue (const String& keyName, const bool defaultValue) const throw() { const ScopedLock sl (lock); const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); if (index >= 0) return properties.getAllValues() [index].getIntValue() != 0; return fallbackProperties != 0 ? 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 == 0 ? var::null : var (xml->createDocument (String::empty, true))); } bool PropertySet::containsKey (const String& keyName) const throw() { const ScopedLock sl (lock); return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); } void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) throw() { 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() throw() : name (0) { } Identifier::Identifier (const Identifier& other) throw() : name (other.name) { } Identifier& Identifier::operator= (const Identifier& other) throw() { 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 (name_.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && name_.isNotEmpty()); } 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 (toString().containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && toString().isNotEmpty()); } Identifier::~Identifier() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Identifier.cpp ***/ /*** Start of inlined file: juce_Variant.cpp ***/ BEGIN_JUCE_NAMESPACE class var::VariantType { public: VariantType() {} virtual ~VariantType() {} virtual int toInt (const ValueUnion&) const { return 0; } virtual double toDouble (const ValueUnion&) const { return 0; } virtual const String toString (const ValueUnion&) const { return String::empty; } virtual bool toBool (const ValueUnion&) const { return false; } virtual DynamicObject* toObject (const ValueUnion&) const { return 0; } virtual bool isVoid() const throw() { return false; } virtual bool isInt() const throw() { return false; } virtual bool isBool() const throw() { return false; } virtual bool isDouble() const throw() { return false; } virtual bool isString() const throw() { return false; } virtual bool isObject() const throw() { return false; } virtual bool isMethod() const throw() { return false; } virtual void cleanUp (ValueUnion&) const throw() {} virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() = 0; virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; }; class var::VariantType_Void : public var::VariantType { public: VariantType_Void() {} static const VariantType_Void instance; bool isVoid() const throw() { return true; } bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const throw() { return otherType.isVoid(); } void writeToStream (const ValueUnion&, OutputStream& output) const { output.writeCompressedInt (0); } }; class var::VariantType_Int : public var::VariantType { public: VariantType_Int() {} static const VariantType_Int instance; int toInt (const ValueUnion& data) const { return data.intValue; }; double toDouble (const ValueUnion& data) const { return (double) data.intValue; } const String toString (const ValueUnion& data) const { return String (data.intValue); } bool toBool (const ValueUnion& data) const { return data.intValue != 0; } bool isInt() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toInt (otherData) == data.intValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (5); output.writeByte (1); output.writeInt (data.intValue); } }; class var::VariantType_Double : public var::VariantType { public: VariantType_Double() {} static const VariantType_Double instance; int toInt (const ValueUnion& data) const { return (int) data.doubleValue; }; double toDouble (const ValueUnion& data) const { return data.doubleValue; } const String toString (const ValueUnion& data) const { return String (data.doubleValue); } bool toBool (const ValueUnion& data) const { return data.doubleValue != 0; } bool isDouble() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toDouble (otherData) == data.doubleValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (9); output.writeByte (4); output.writeDouble (data.doubleValue); } }; class var::VariantType_Bool : public var::VariantType { public: VariantType_Bool() {} static const VariantType_Bool instance; int toInt (const ValueUnion& data) const { return data.boolValue ? 1 : 0; }; double toDouble (const ValueUnion& data) const { return data.boolValue ? 1.0 : 0.0; } const String toString (const ValueUnion& data) const { return String::charToString (data.boolValue ? '1' : '0'); } bool toBool (const ValueUnion& data) const { return data.boolValue; } bool isBool() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toBool (otherData) == data.boolValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { output.writeCompressedInt (1); output.writeByte (data.boolValue ? 2 : 3); } }; class var::VariantType_String : public var::VariantType { public: VariantType_String() {} static const VariantType_String instance; void cleanUp (ValueUnion& data) const throw() { delete data.stringValue; } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.stringValue = new String (*source.stringValue); } int toInt (const ValueUnion& data) const { return data.stringValue->getIntValue(); }; double toDouble (const ValueUnion& data) const { return data.stringValue->getDoubleValue(); } const String toString (const ValueUnion& data) const { return *data.stringValue; } bool toBool (const ValueUnion& data) const { return data.stringValue->getIntValue() != 0 || data.stringValue->trim().equalsIgnoreCase ("true") || data.stringValue->trim().equalsIgnoreCase ("yes"); } bool isString() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { return otherType.toString (otherData) == *data.stringValue; } void writeToStream (const ValueUnion& data, OutputStream& output) const { const int len = data.stringValue->getNumBytesAsUTF8() + 1; output.writeCompressedInt (len + 1); output.writeByte (5); HeapBlock temp (len); data.stringValue->copyToUTF8 (temp, len); output.write (temp, len); } }; class var::VariantType_Object : public var::VariantType { public: VariantType_Object() {} static const VariantType_Object instance; void cleanUp (ValueUnion& data) const throw() { if (data.objectValue != 0) data.objectValue->decReferenceCount(); } void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.objectValue = source.objectValue; if (dest.objectValue != 0) dest.objectValue->incReferenceCount(); } const String toString (const ValueUnion& data) const { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } bool toBool (const ValueUnion& data) const { return data.objectValue != 0; } DynamicObject* toObject (const ValueUnion& data) const { return data.objectValue; } bool isObject() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { 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_Method : public var::VariantType { public: VariantType_Method() {} static const VariantType_Method instance; const String toString (const ValueUnion&) const { return "Method"; } bool toBool (const ValueUnion& data) const { return data.methodValue != 0; } bool isMethod() const throw() { return true; } bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const throw() { 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_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_Method var::VariantType_Method::instance; var::var() throw() : type (&VariantType_Void::instance) { } var::~var() throw() { 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_) throw() : type (&VariantType_Int::instance) { value.intValue = value_; } var::var (const bool value_) throw() : type (&VariantType_Bool::instance) { value.boolValue = value_; } var::var (const double value_) throw() : type (&VariantType_Double::instance) { value.doubleValue = value_; } var::var (const String& value_) : type (&VariantType_String::instance) { value.stringValue = new String (value_); } var::var (const char* const value_) : type (&VariantType_String::instance) { value.stringValue = new String (value_); } var::var (const juce_wchar* const value_) : type (&VariantType_String::instance) { value.stringValue = new String (value_); } var::var (DynamicObject* const object) : type (&VariantType_Object::instance) { value.objectValue = object; if (object != 0) object->incReferenceCount(); } var::var (MethodFunction method_) throw() : type (&VariantType_Method::instance) { value.methodValue = method_; } bool var::isVoid() const throw() { return type->isVoid(); } bool var::isInt() const throw() { return type->isInt(); } bool var::isBool() const throw() { return type->isBool(); } bool var::isDouble() const throw() { return type->isDouble(); } bool var::isString() const throw() { return type->isString(); } bool var::isObject() const throw() { return type->isObject(); } bool var::isMethod() const throw() { return type->isMethod(); } var::operator int() const { return type->toInt (value); } var::operator bool() const { return type->toBool (value); } var::operator float() const { return (float) type->toDouble (value); } var::operator double() const { return type->toDouble (value); } const String var::toString() const { return type->toString (value); } var::operator const String() const { return type->toString (value); } DynamicObject* var::getObject() const { return type->toObject (value); } void var::swapWith (var& other) throw() { swapVariables (type, other.type); swapVariables (value, other.value); } var& var::operator= (const var& newValue) { type->cleanUp (value); type = newValue.type; type->createCopy (value, newValue.value); return *this; } var& var::operator= (int newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (bool newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (double newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const char* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const juce_wchar* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (const String& newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (DynamicObject* newValue) { var v (newValue); swapWith (v); return *this; } var& var::operator= (MethodFunction newValue) { var v (newValue); swapWith (v); return *this; } bool var::equals (const var& other) const throw() { return type->equals (value, other.value, *other.type); } bool operator== (const var& v1, const var& v2) throw() { return v1.equals (v2); } bool operator!= (const var& v1, const var& v2) throw() { return ! v1.equals (v2); } bool operator== (const var& v1, const String& v2) throw() { return v1.toString() == v2; } bool operator!= (const var& v1, const String& v2) throw() { return v1.toString() != v2; } void var::writeToStream (OutputStream& output) const { type->writeToStream (value, output); } const var var::readFromStream (InputStream& input) { const int numBytes = input.readCompressedInt(); if (numBytes > 0) { switch (input.readByte()) { case 1: return var (input.readInt()); case 2: return var (true); case 3: return var (false); case 4: return var (input.readDouble()); case 5: { MemoryOutputStream mo; mo.writeFromInputStream (input, numBytes - 1); return var (mo.toUTF8()); } default: input.skipNextBytes (numBytes - 1); break; } } return var::null; } const var var::operator[] (const Identifier& propertyName) const { DynamicObject* const o = getObject(); return o != 0 ? o->getProperty (propertyName) : var::null; } const var var::invoke (const Identifier& method, const var* arguments, int numArguments) const { DynamicObject* const o = getObject(); return o != 0 ? o->invokeMethod (method, arguments, numArguments) : var::null; } const var var::invoke (const var& targetObject, const var* arguments, int numArguments) const { if (isMethod()) { DynamicObject* const target = targetObject.getObject(); if (target != 0) return (target->*(value.methodValue)) (arguments, numArguments); } return var::null; } const var var::call (const Identifier& method) const { return invoke (method, 0, 0); } const var var::call (const Identifier& method, const var& arg1) const { return invoke (method, &arg1, 1); } const var var::call (const Identifier& method, const var& arg1, const var& arg2) const { var args[] = { arg1, arg2 }; return invoke (method, args, 2); } const 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); } const 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); } const 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); } END_JUCE_NAMESPACE /*** End of inlined file: juce_Variant.cpp ***/ /*** Start of inlined file: juce_NamedValueSet.cpp ***/ BEGIN_JUCE_NAMESPACE NamedValueSet::NamedValue::NamedValue() throw() { } inline NamedValueSet::NamedValue::NamedValue (const Identifier& name_, const var& value_) : name (name_), value (value_) { } bool NamedValueSet::NamedValue::operator== (const NamedValueSet::NamedValue& other) const throw() { return name == other.name && value == other.value; } NamedValueSet::NamedValueSet() throw() { } 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 != 0 && i2 != 0) { 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 throw() { return values.size(); } const var& NamedValueSet::operator[] (const Identifier& name) const { for (NamedValue* i = values; i != 0; i = i->nextListItem) if (i->name == name) return i->value; return var::null; } const var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const { const var* v = getVarPointer (name); return v != 0 ? *v : defaultReturnValue; } var* NamedValueSet::getVarPointer (const Identifier& name) const { for (NamedValue* i = values; i != 0; i = i->nextListItem) if (i->name == name) return &(i->value); return 0; } bool NamedValueSet::set (const Identifier& name, const var& newValue) { LinkedListPointer* i = &values; while (i->get() != 0) { NamedValue* const v = i->get(); if (v->name == name) { if (v->value == 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) != 0; } bool NamedValueSet::remove (const Identifier& name) { LinkedListPointer* i = &values; for (;;) { NamedValue* const v = i->get(); if (v == 0) 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 != 0); return v->name; } const var NamedValueSet::getValueAt (const int index) const { const NamedValue* const v = values[index]; jassert (v != 0); 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 != 0; 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 { var* const v = properties.getVarPointer (propertyName); return v != 0 && ! 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].invoke (var (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::Helpers { public: typedef ReferenceCountedObjectPtr TermPtr; // This helper function is needed to work around VC6 scoping bugs static const TermPtr& getTermFor (const Expression& exp) throw() { return exp.term; } friend class Expression::Term; // (also only needed as a VC6 workaround) class Constant : public Term { public: Constant (const double value_, bool isResolutionTarget_) : value (value_), isResolutionTarget (isResolutionTarget_) {} Type getType() const throw() { return constantType; } Term* clone() const { return new Constant (value, isResolutionTarget); } double evaluate (const EvaluationContext&, int) const { return value; } int getNumInputs() const { return 0; } Term* getInput (int) const { return 0; } const TermPtr negated() { return new Constant (-value, isResolutionTarget); } const String toString() const { if (isResolutionTarget) return "@" + String (value); return String (value); } double value; bool isResolutionTarget; }; class Symbol : public Term { public: explicit Symbol (const String& symbol_) : mainSymbol (symbol_.upToFirstOccurrenceOf (".", false, false).trim()), member (symbol_.fromFirstOccurrenceOf (".", false, false).trim()) {} Symbol (const String& symbol_, const String& member_) : mainSymbol (symbol_), member (member_) {} double evaluate (const EvaluationContext& c, int recursionDepth) const { if (++recursionDepth > 256) throw EvaluationError ("Recursive symbol references"); try { return getTermFor (c.getSymbolValue (mainSymbol, member))->evaluate (c, recursionDepth); } catch (...) {} return 0; } Type getType() const throw() { return symbolType; } Term* clone() const { return new Symbol (mainSymbol, member); } int getNumInputs() const { return 0; } Term* getInput (int) const { return 0; } const String toString() const { return joinParts (mainSymbol, member); } void getSymbolParts (String& objectName, String& memberName) const { objectName = mainSymbol; memberName = member; } static const String joinParts (const String& mainSymbol, const String& member) { return member.isEmpty() ? mainSymbol : mainSymbol + "." + member; } bool referencesSymbol (const String& s, const EvaluationContext* c, int recursionDepth) const { if (s == mainSymbol) return true; if (++recursionDepth > 256) throw EvaluationError ("Recursive symbol references"); try { return c != 0 && getTermFor (c->getSymbolValue (mainSymbol, member))->referencesSymbol (s, c, recursionDepth); } catch (EvaluationError&) { return false; } } String mainSymbol, member; }; class Function : public Term { public: Function (const String& functionName_, const ReferenceCountedArray& parameters_) : functionName (functionName_), parameters (parameters_) {} Term* clone() const { return new Function (functionName, parameters); } double evaluate (const EvaluationContext& c, int recursionDepth) const { HeapBlock params (parameters.size()); for (int i = 0; i < parameters.size(); ++i) params[i] = parameters.getUnchecked(i)->evaluate (c, recursionDepth); return c.evaluateFunction (functionName, params, parameters.size()); } Type getType() const throw() { return functionType; } int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); } int getNumInputs() const { return parameters.size(); } Term* getInput (int i) const { return parameters [i]; } const String getFunctionName() const { return functionName; } bool referencesSymbol (const String& s, const EvaluationContext* c, int recursionDepth) const { for (int i = 0; i < parameters.size(); ++i) if (parameters.getUnchecked(i)->referencesSymbol (s, c, recursionDepth)) return true; return false; } const String toString() const { if (parameters.size() == 0) return functionName + "()"; String s (functionName + " ("); for (int i = 0; i < parameters.size(); ++i) { s << parameters.getUnchecked(i)->toString(); if (i < parameters.size() - 1) s << ", "; } s << ')'; return s; } const String functionName; ReferenceCountedArray parameters; }; class Negate : public Term { public: explicit Negate (const TermPtr& input_) : input (input_) { jassert (input_ != 0); } Type getType() const throw() { 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 ? static_cast (input) : 0; } Term* clone() const { return new Negate (input->clone()); } double evaluate (const EvaluationContext& c, int recursionDepth) const { return -input->evaluate (c, recursionDepth); } const String getFunctionName() const { return "-"; } const TermPtr negated() { return input; } const TermPtr createTermToEvaluateInput (const EvaluationContext& context, const Term* input_, double overallTarget, Term* topLevelTerm) const { (void) input_; jassert (input_ == input); const Term* const dest = findDestinationFor (topLevelTerm, this); return new Negate (dest == 0 ? new Constant (overallTarget, false) : dest->createTermToEvaluateInput (context, this, overallTarget, topLevelTerm)); } const String toString() const { if (input->getOperatorPrecedence() > 0) return "-(" + input->toString() + ")"; else return "-" + input->toString(); } bool referencesSymbol (const String& s, const EvaluationContext* c, int recursionDepth) const { return input->referencesSymbol (s, c, recursionDepth); } private: const TermPtr input; }; class BinaryTerm : public Term { public: BinaryTerm (Term* const left_, Term* const right_) : left (left_), right (right_) { jassert (left_ != 0 && right_ != 0); } int getInputIndexFor (const Term* possibleInput) const { return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1); } Type getType() const throw() { return operatorType; } int getNumInputs() const { return 2; } Term* getInput (int index) const { return index == 0 ? static_cast (left) : (index == 1 ? static_cast (right) : 0); } bool referencesSymbol (const String& s, const EvaluationContext* c, int recursionDepth) const { return left->referencesSymbol (s, c, recursionDepth) || right->referencesSymbol (s, c, recursionDepth); } const String toString() const { String s; const int ourPrecendence = getOperatorPrecedence(); if (left->getOperatorPrecedence() > ourPrecendence) s << '(' << left->toString() << ')'; else s = left->toString(); s << ' ' << getFunctionName() << ' '; if (right->getOperatorPrecedence() >= ourPrecendence) s << '(' << right->toString() << ')'; else s << right->toString(); return s; } protected: const TermPtr left, right; const TermPtr createDestinationTerm (const EvaluationContext& context, const Term* input, double overallTarget, Term* topLevelTerm) const { jassert (input == left || input == right); if (input != left && input != right) return 0; const Term* const dest = findDestinationFor (topLevelTerm, this); if (dest == 0) return new Constant (overallTarget, false); return dest->createTermToEvaluateInput (context, this, overallTarget, topLevelTerm); } }; 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 evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) + right->evaluate (c, recursionDepth); } int getOperatorPrecedence() const { return 2; } const String getFunctionName() const { return "+"; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; 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 evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) - right->evaluate (c, recursionDepth); } int getOperatorPrecedence() const { return 2; } const String getFunctionName() const { return "-"; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; 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 evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) * right->evaluate (c, recursionDepth); } const String getFunctionName() const { return "*"; } int getOperatorPrecedence() const { return 1; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; 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 evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) / right->evaluate (c, recursionDepth); } const String getFunctionName() const { return "/"; } int getOperatorPrecedence() const { return 1; } const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const { const TermPtr newDest (createDestinationTerm (c, input, overallTarget, topLevelTerm)); if (newDest == 0) return 0; 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 != 0) return t; } return 0; } static Constant* findTermToAdjust (Term* const term, const bool mustBeFlagged) { { Constant* const c = dynamic_cast (term); if (c != 0 && (c->isResolutionTarget || ! mustBeFlagged)) return c; } if (dynamic_cast (term) != 0) return 0; int i; const int numIns = term->getNumInputs(); for (i = 0; i < numIns; ++i) { Constant* const c = dynamic_cast (term->getInput (i)); if (c != 0 && (c->isResolutionTarget || ! mustBeFlagged)) return c; } for (i = 0; i < numIns; ++i) { Constant* const c = findTermToAdjust (term->getInput (i), mustBeFlagged); if (c != 0) return c; } return 0; } 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; } static bool renameSymbol (Term* const t, const String& oldName, const String& newName) { Symbol* const sym = dynamic_cast (t); if (sym != 0 && sym->mainSymbol == oldName) { sym->mainSymbol = newName; return true; } bool anyChanged = false; for (int i = t->getNumInputs(); --i >= 0;) if (renameSymbol (t->getInput (i), oldName, newName)) anyChanged = true; return anyChanged; } class Parser { public: Parser (const String& stringToParse, int& textIndex_) : textString (stringToParse), textIndex (textIndex_) { text = textString; } const TermPtr readExpression() { TermPtr lhs (readMultiplyOrDivideExpression()); char opType; while (lhs != 0 && readOperator ("+-", &opType)) { TermPtr rhs (readMultiplyOrDivideExpression()); if (rhs == 0) throw ParseError ("Expected expression after \"" + String::charToString (opType) + "\""); if (opType == '+') lhs = new Add (lhs, rhs); else lhs = new Subtract (lhs, rhs); } return lhs; } private: const String textString; const juce_wchar* text; int& textIndex; static inline bool isDecimalDigit (const juce_wchar c) throw() { return c >= '0' && c <= '9'; } void skipWhitespace (int& i) throw() { while (CharacterFunctions::isWhitespace (text [i])) ++i; } bool readChar (const juce_wchar required) throw() { if (text[textIndex] == required) { ++textIndex; return true; } return false; } bool readOperator (const char* ops, char* const opType = 0) throw() { skipWhitespace (textIndex); while (*ops != 0) { if (readChar (*ops)) { if (opType != 0) *opType = *ops; return true; } ++ops; } return false; } bool readIdentifier (String& identifier) throw() { skipWhitespace (textIndex); int i = textIndex; if (CharacterFunctions::isLetter (text[i]) || text[i] == '_') { ++i; while (CharacterFunctions::isLetterOrDigit (text[i]) || text[i] == '_' || text[i] == '.') ++i; } if (i > textIndex) { identifier = String (text + textIndex, i - textIndex); textIndex = i; return true; } return false; } Term* readNumber() throw() { skipWhitespace (textIndex); int i = textIndex; const bool isResolutionTarget = (text[i] == '@'); if (isResolutionTarget) { ++i; skipWhitespace (i); textIndex = i; } if (text[i] == '-') { ++i; skipWhitespace (i); } int numDigits = 0; while (isDecimalDigit (text[i])) { ++i; ++numDigits; } const bool hasPoint = (text[i] == '.'); if (hasPoint) { ++i; while (isDecimalDigit (text[i])) { ++i; ++numDigits; } } if (numDigits == 0) return 0; juce_wchar c = text[i]; const bool hasExponent = (c == 'e' || c == 'E'); if (hasExponent) { ++i; c = text[i]; if (c == '+' || c == '-') ++i; int numExpDigits = 0; while (isDecimalDigit (text[i])) { ++i; ++numExpDigits; } if (numExpDigits == 0) return 0; } const int start = textIndex; textIndex = i; return new Constant (String (text + start, i - start).getDoubleValue(), isResolutionTarget); } const TermPtr readMultiplyOrDivideExpression() { TermPtr lhs (readUnaryExpression()); char opType; while (lhs != 0 && readOperator ("*/", &opType)) { TermPtr rhs (readUnaryExpression()); if (rhs == 0) 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 == 0) 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 != 0) return e; e = readNumber(); if (e != 0) return e; String identifier; if (readIdentifier (identifier)) { if (readOperator ("(")) // method call... { Function* const f = new Function (identifier, ReferenceCountedArray()); ScopedPointer func (f); // (can't use ScopedPointer in MSVC) TermPtr param (readExpression()); if (param == 0) { if (readOperator (")")) return func.release(); throw ParseError ("Expected parameters after \"" + identifier + " (\""); } f->parameters.add (param); while (readOperator (",")) { param = readExpression(); if (param == 0) throw ParseError ("Expected expression after \",\""); f->parameters.add (param); } if (readOperator (")")) return func.release(); throw ParseError ("Expected \")\""); } else // just a symbol.. { return new Symbol (identifier); } } return 0; } const TermPtr readParenthesisedExpression() { if (! readOperator ("(")) return 0; const TermPtr e (readExpression()); if (e == 0 || ! readOperator (")")) return 0; 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 != 0); } 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) { int i = 0; Helpers::Parser parser (stringToParse, i); term = parser.readExpression(); if (term == 0) term = new Helpers::Constant (0, false); } const Expression Expression::parse (const String& stringToParse, int& textIndexToStartFrom) { Helpers::Parser parser (stringToParse, textIndexToStartFrom); const Helpers::TermPtr term (parser.readExpression()); if (term != 0) return Expression (term); return Expression(); } double Expression::evaluate() const { return evaluate (Expression::EvaluationContext()); } double Expression::evaluate (const Expression::EvaluationContext& context) const { return term->evaluate (context, 0); } const Expression Expression::operator+ (const Expression& other) const { return Expression (new Helpers::Add (term, other.term)); } const Expression Expression::operator- (const Expression& other) const { return Expression (new Helpers::Subtract (term, other.term)); } const Expression Expression::operator* (const Expression& other) const { return Expression (new Helpers::Multiply (term, other.term)); } const Expression Expression::operator/ (const Expression& other) const { return Expression (new Helpers::Divide (term, other.term)); } const Expression Expression::operator-() const { return Expression (term->negated()); } const String Expression::toString() const { return term->toString(); } const Expression Expression::symbol (const String& symbol) { return Expression (new Helpers::Symbol (symbol)); } const Expression Expression::function (const String& functionName, const Array& parameters) { ReferenceCountedArray params; for (int i = 0; i < parameters.size(); ++i) params.add (parameters.getReference(i).term); return Expression (new Helpers::Function (functionName, params)); } const Expression Expression::adjustedToGiveNewResult (const double targetValue, const Expression::EvaluationContext& context) const { ScopedPointer newTerm (term->clone()); Helpers::Constant* termToAdjust = Helpers::findTermToAdjust (newTerm, true); if (termToAdjust == 0) termToAdjust = Helpers::findTermToAdjust (newTerm, false); if (termToAdjust == 0) { newTerm = new Helpers::Add (newTerm.release(), new Helpers::Constant (0, false)); termToAdjust = Helpers::findTermToAdjust (newTerm, false); } jassert (termToAdjust != 0); const Term* const parent = Helpers::findDestinationFor (newTerm, termToAdjust); if (parent == 0) { termToAdjust->value = targetValue; } else { const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (context, termToAdjust, targetValue, newTerm)); if (reverseTerm == 0) return Expression (targetValue); termToAdjust->value = reverseTerm->evaluate (context, 0); } return Expression (newTerm.release()); } const Expression Expression::withRenamedSymbol (const String& oldSymbol, const String& newSymbol) const { jassert (newSymbol.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); if (oldSymbol == newSymbol) return *this; Expression newExpression (term->clone()); Helpers::renameSymbol (newExpression.term, oldSymbol, newSymbol); return newExpression; } bool Expression::referencesSymbol (const String& symbol, const EvaluationContext* context) const { return term->referencesSymbol (symbol, context, 0); } bool Expression::usesAnySymbols() const { return Helpers::containsAnySymbols (term); } Expression::Type Expression::getType() const throw() { return term->getType(); } const String Expression::getSymbol() const { String objectName, memberName; term->getSymbolParts (objectName, memberName); return Expression::Helpers::Symbol::joinParts (objectName, memberName); } void Expression::getSymbolParts (String& objectName, String& memberName) const { term->getSymbolParts (objectName, memberName); } const String Expression::getFunction() const { return term->getFunctionName(); } const String Expression::getOperator() const { return term->getFunctionName(); } int Expression::getNumInputs() const { return term->getNumInputs(); } const Expression Expression::getInput (int index) const { return Expression (term->getInput (index)); } int Expression::Term::getOperatorPrecedence() const { return 0; } bool Expression::Term::referencesSymbol (const String&, const EvaluationContext*, int) const { return false; } int Expression::Term::getInputIndexFor (const Term*) const { return -1; } const ReferenceCountedObjectPtr Expression::Term::createTermToEvaluateInput (const EvaluationContext&, const Term*, double, Term*) const { jassertfalse; return 0; } const ReferenceCountedObjectPtr Expression::Term::negated() { return new Helpers::Negate (this); } void Expression::Term::getSymbolParts (String&, String&) const { jassertfalse; // You should only call getSymbol() on an expression that's actually a symbol! } const String Expression::Term::getFunctionName() const { jassertfalse; // You shouldn't call this for an expression that's not actually a function! return String::empty; } Expression::ParseError::ParseError (const String& message) : description (message) { DBG ("Expression::ParseError: " + message); } Expression::EvaluationError::EvaluationError (const String& message) : description (message) { DBG ("Expression::EvaluationError: " + description); } Expression::EvaluationError::EvaluationError (const String& symbol, const String& member) : description ("Unknown symbol: \"" + symbol + (member.isEmpty() ? "\"" : ("." + member + "\""))) { DBG ("Expression::EvaluationError: " + description); } Expression::EvaluationContext::EvaluationContext() {} Expression::EvaluationContext::~EvaluationContext() {} const Expression Expression::EvaluationContext::getSymbolValue (const String& symbol, const String& member) const { throw EvaluationError (symbol, member); } double Expression::EvaluationContext::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 EvaluationError ("Unknown function: \"" + functionName + "\""); } 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 != 0); 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 throw() { 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 throw() { uint32 l = data1; uint32 r = data2; for (int i = 0; i < 16; ++i) { l ^= p[i]; r ^= F(l); swapVariables (l, r); } data1 = r ^ p[17]; data2 = l ^ p[16]; } void BlowFish::decrypt (uint32& data1, uint32& data2) const throw() { uint32 l = data1; uint32 r = data2; for (int i = 17; i > 1; --i) { l ^= p[i]; r ^= F(l); swapVariables (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; const int len = text.length(); const juce_wchar* const t = text; for (int i = 0; i < len; ++i) { // 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[i]); 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) { const ScopedPointer fin (file.createInputStream()); if (fin != 0) processStream (*fin, -1); else zerostruct (result); } MD5::~MD5() { } namespace MD5Functions { void encode (void* const output, const void* const input, const int numBytes) throw() { 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) throw() { return (x << n) | (x >> (32 - n)); } inline uint32 F (const uint32 x, const uint32 y, const uint32 z) throw() { return (x & y) | (~x & z); } inline uint32 G (const uint32 x, const uint32 y, const uint32 z) throw() { return (x & z) | (y & ~z); } inline uint32 H (const uint32 x, const uint32 y, const uint32 z) throw() { return x ^ y ^ z; } inline uint32 I (const uint32 x, const uint32 y, const uint32 z) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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]; zeromem (paddingBuffer, paddingLength); paddingBuffer [0] = 0x80; 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); } const MemoryBlock MD5::getRawChecksumData() const { return MemoryBlock (result, sizeof (result)); } const 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; } } const 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 (0); 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 throw() { return part1 == other.part1 && part2 == other.part2; } bool RSAKey::operator!= (const RSAKey& other) const throw() { return ! operator== (other); } const 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; } const 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 == 0 ? 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; } const 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); } const 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); } const 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 static Array activeStreams; void juce_CheckForDanglingStreams() { /* 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); }; #endif OutputStream::OutputStream() : newLineString (NewLine::getDefault()) { #if JUCE_DEBUG activeStreams.add (this); #endif } OutputStream::~OutputStream() { #if JUCE_DEBUG 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::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 asUnicode, const bool writeUnicodeHeaderBytes) { if (asUnicode) { if (writeUnicodeHeaderBytes) write ("\x0ff\x0fe", 2); const juce_wchar* src = text; bool lastCharWasReturn = false; while (*src != 0) { if (*src == L'\n' && ! lastCharWasReturn) writeShort ((short) L'\r'); lastCharWasReturn = (*src == L'\r'); writeShort ((short) *src++); } } 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 != 0) 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 (0, 0, 0, 0, 0, 0); } 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 != 0) { if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) return true; subIterator = 0; } 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 != 0) *isHiddenResult = isHidden; if (isDirResult != 0) *isDirResult = isDirectory; return true; } else if (subIterator != 0) { return next(); } } } return false; } const File DirectoryIterator::getFile() const { if (subIterator != 0 && 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 != 0) ? 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) { } const 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; const 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.. String path (p.replaceCharacter ('\\', '/')); 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 != 0) 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; } const 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; } const 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; } const File File::getParentDirectory() const { return File (getPathUpToLastSlash(), (int) 0); } const 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(); } const 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 ('/') || path.startsWithChar ('\\') #if JUCE_WINDOWS || (path.isNotEmpty() && path[1] == ':'); #else || path.startsWithChar ('~'); #endif } const File File::getChildFile (String relativePath) const { if (isAbsolutePath (relativePath)) { // the path is really absolute.. return File (relativePath); } else { // it's relative, so remove any ../ or ./ bits at the start. String path (fullPath); if (relativePath[0] == '.') { #if JUCE_WINDOWS relativePath = relativePath.replaceCharacter ('/', '\\').trimStart(); #else relativePath = relativePath.replaceCharacter ('\\', '/').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); } } const File File::getSiblingFile (const String& fileName) const { return getParentDirectory().getChildFile (fileName); } const String File::descriptionOfSizeInBytes (const int64 bytes) { if (bytes == 1) { return "1 byte"; } else if (bytes < 1024) { return String ((int) 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"; } } bool File::create() const { if (exists()) return true; { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; FileOutputStream fo (*this, 8); } return exists(); } bool File::createDirectory() const { if (! isDirectory()) { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); return isDirectory(); } return true; } const Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } 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); } 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); } const 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; } const 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 { if (putNumbersInBrackets) f = getChildFile (prefix + '(' + String (num++) + ')' + suffix); else f = getChildFile (prefix + String (num++) + suffix); } while (f.exists()); } return f; } const File File::getNonexistentSibling (const bool putNumbersInBrackets) const { if (exists()) { return getParentDirectory() .getNonexistentChildFile (getFileNameWithoutExtension(), getFileExtension(), putNumbersInBrackets); } else { return *this; } } const String File::getFileExtension() const { String ext; if (! isDirectory()) { const int indexOfDot = fullPath.lastIndexOfChar ('.'); if (indexOfDot > fullPath.lastIndexOfChar (separator)) ext = fullPath.substring (indexOfDot); } return ext; } 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; } const 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 0; } FileOutputStream* File::createOutputStream (const int bufferSize) const { ScopedPointer out (new FileOutputStream (*this, bufferSize)); if (out->failedToOpen()) return 0; 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 != 0) { 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, buffer2; buffer1.malloc (bufferSize); buffer2.malloc (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; } const 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); } const 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; } const String File::getRelativePathFrom (const File& dir) const { String thisPath (fullPath); { int len = thisPath.length(); while (--len >= 0 && thisPath [len] == File::separator) thisPath [len] = 0; } String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() : dir.fullPath)); const int len = jmin (thisPath.length(), dirPath.length()); int commonBitLength = 0; for (int i = 0; i < len; ++i) { #if NAMES_ARE_CASE_SENSITIVE if (thisPath[i] != dirPath[i]) #else if (CharacterFunctions::toLowerCase (thisPath[i]) != CharacterFunctions::toLowerCase (dirPath[i])) #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; } const 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); else 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.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); expect (tempFile.loadFileAsString() == "0123456789"); expect (! demoFolder.containsSubDirectories()); 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'); } 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 (0), currentPosition (0), totalSize (0), 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 (0), currentPosition (0), bufferSize (bufferSize_), bytesInBuffer (0), buffer (jmax (bufferSize_, 16)) { openHandle(); } FileOutputStream::~FileOutputStream() { flush(); closeHandle(); } int64 FileOutputStream::getPosition() { return currentPosition; } bool FileOutputStream::setPosition (int64 newPosition) { if (newPosition != currentPosition) { flush(); currentPosition = juce_fileSetPosition (fileHandle, newPosition); } return newPosition == currentPosition; } void FileOutputStream::flush() { if (bytesInBuffer > 0) { writeInternal (buffer, bytesInBuffer); bytesInBuffer = 0; } 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 (bytesInBuffer > 0) { // flush the reservoir const bool wroteOk = (writeInternal (buffer, bytesInBuffer) == bytesInBuffer); bytesInBuffer = 0; if (! wroteOk) 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; } 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(); } const File FileSearchPath::operator[] (const int index) const { return File (directories [index]); } const 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 (0) { } 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 != 0; } const 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 #if JUCE_MSVC #pragma warning (push) #pragma warning (disable : 4127 4389 4018) #endif #else #if JUCE_LINUX #include #include #include #include #include #elif (MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4) && ! JUCE_IOS #include #endif #include #include #include #include #endif BEGIN_JUCE_NAMESPACE #if defined (JUCE_LINUX) || defined (JUCE_MAC) || defined (JUCE_IOS) typedef socklen_t juce_socklen_t; #else typedef int juce_socklen_t; #endif #if JUCE_WINDOWS namespace SocketHelpers { typedef int (__stdcall juce_CloseWin32SocketLibCall) (void); static juce_CloseWin32SocketLibCall* juce_CloseWin32SocketLib = 0; void initWin32Sockets() { static CriticalSection lock; const ScopedLock sl (lock); if (SocketHelpers::juce_CloseWin32SocketLib == 0) { WSADATA wsaData; const WORD wVersionRequested = MAKEWORD (1, 1); WSAStartup (wVersionRequested, &wsaData); SocketHelpers::juce_CloseWin32SocketLib = &WSACleanup; } } } void juce_shutdownWin32Sockets() { if (SocketHelpers::juce_CloseWin32SocketLib != 0) (*SocketHelpers::juce_CloseWin32SocketLib)(); } #endif namespace SocketHelpers { bool resetSocketOptions (const int handle, const bool isDatagram, const bool allowBroadcast) throw() { 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) throw() { if (handle <= 0 || port <= 0) return false; struct sockaddr_in servTmpAddr; zerostruct (servTmpAddr); 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) throw() { 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) throw() { 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 : 0; fd_set* const pwset = forReading ? 0 : &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; } if ((forReading && FD_ISSET (handle, &rset)) || ((! forReading) && FD_ISSET (handle, &wset))) return 1; return 0; } bool setSocketBlockingState (const int handle, const bool shouldBlock) throw() { #if JUCE_WINDOWS u_long nonBlocking = shouldBlock ? 0 : 1; if (ioctlsocket (handle, FIONBIO, &nonBlocking) != 0) return false; #else int socketFlags = fcntl (handle, F_GETFL, 0); if (socketFlags == -1) return false; if (shouldBlock) socketFlags &= ~O_NONBLOCK; else socketFlags |= O_NONBLOCK; if (fcntl (handle, F_SETFL, socketFlags) != 0) return false; #endif return true; } bool connectSocket (int volatile& handle, const bool isDatagram, void** serverAddress, const String& hostName, const int portNumber, const int timeOutMillisecs) throw() { struct hostent* const hostEnt = gethostbyname (hostName.toUTF8()); if (hostEnt == 0) return false; struct in_addr targetAddress; memcpy (&targetAddress.s_addr, *(hostEnt->h_addr_list), sizeof (targetAddress.s_addr)); struct sockaddr_in servTmpAddr; zerostruct (servTmpAddr); servTmpAddr.sin_family = PF_INET; servTmpAddr.sin_addr = targetAddress; servTmpAddr.sin_port = htons ((uint16) portNumber); if (handle < 0) handle = (int) socket (AF_INET, isDatagram ? SOCK_DGRAM : SOCK_STREAM, 0); if (handle < 0) return false; if (isDatagram) { *serverAddress = new struct sockaddr_in(); *((struct sockaddr_in*) *serverAddress) = servTmpAddr; return true; } setSocketBlockingState (handle, false); const int result = ::connect (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)); 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) { #if JUCE_WINDOWS SocketHelpers::initWin32Sockets(); #endif } StreamingSocket::StreamingSocket (const String& hostName_, const int portNumber_, const int handle_) : hostName (hostName_), portNumber (portNumber_), handle (handle_), connected (true), isListener (false) { #if JUCE_WINDOWS SocketHelpers::initWin32Sockets(); #endif 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; zerostruct (servTmpAddr); 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 0; } bool StreamingSocket::isLocal() const throw() { return hostName == "127.0.0.1"; } DatagramSocket::DatagramSocket (const int localPortNumber, const bool allowBroadcast_) : portNumber (0), handle (-1), connected (true), allowBroadcast (allowBroadcast_), serverAddress (0) { #if JUCE_WINDOWS SocketHelpers::initWin32Sockets(); #endif 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) { #if JUCE_WINDOWS SocketHelpers::initWin32Sockets(); #endif 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 0; } 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 throw() { 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 { const 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]) || CharacterFunctions::indexOfChar (L"+-.", url[i], false) >= 0) ++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: " << (unsigned int) postData.getSize() << "\r\n"; } } } const 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(); } const 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); } const 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); } const 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 != 0) { in->readIntoMemoryBlock (destData); return true; } return false; } const String URL::readEntireTextStream (const bool usePostCommand) const { const ScopedPointer in (createInputStream (usePostCommand)); if (in != 0) 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; } const 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(), 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()); } const String URL::addEscapeChars (const String& s, const bool isParameter) { const char* const legalChars = isParameter ? "_-.*!'()" : ",$_-.*!'()"; Array utf8 (s.toUTF8(), s.getNumBytesAsUTF8()); for (int i = 0; i < utf8.size(); ++i) { const char c = utf8.getUnchecked(i); if (! (CharacterFunctions::isLetterOrDigit (c) || CharacterFunctions::indexOfChar (legalChars, c, false) >= 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; s.preallocateStorage (18); 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 throw() { int64 n = 0; for (int i = numElementsInArray (asBytes); --i >= 0;) n = (n << 8) | asBytes[i]; return n; } bool MACAddress::isNull() const throw() { return asInt64 == 0; } bool MACAddress::operator== (const MACAddress& other) const throw() { return asInt64 == other.asInt64; } bool MACAddress::operator!= (const MACAddress& other) const throw() { 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) throw() { // You need to supply a real stream when creating a BufferedInputStream jassert (source != 0); 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), sourceToDelete (deleteSourceWhenDestroyed ? sourceStream : 0), 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), 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; } } const 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) { internalCopy.append (data, sourceDataSize); data = static_cast (internalCopy.getData()); } } MemoryInputStream::MemoryInputStream (const MemoryBlock& sourceData, const bool keepInternalCopy) : data (static_cast (sourceData.getData())), dataSize (sourceData.getSize()), position (0) { if (keepInternalCopy) { internalCopy = sourceData; data = static_cast (internalCopy.getData()); } } 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)); 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; for (int i = 50; --i >= 0;) randomString << (juce_wchar) (Random::getSystemRandom().nextInt() & 0xffff); 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); expect (mi.readString() == randomString); expect (mi.readInt64() == randomInt64); expect (mi.readInt64BigEndian() == randomInt64); expect (mi.readDouble() == randomDouble); expect (mi.readDoubleBigEndian() == randomDouble); } }; 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() { flush(); } void MemoryOutputStream::flush() { if (&data != &internalBlock) data.setSize (size, false); } void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) { data.ensureSize (bytesToPreallocate + 1); } void MemoryOutputStream::reset() throw() { position = 0; size = 0; } bool MemoryOutputStream::write (const void* const buffer, int howMany) { if (howMany > 0) { const size_t storageNeeded = position + howMany; if (storageNeeded >= data.getSize()) data.ensureSize ((storageNeeded + jmin ((int) (storageNeeded / 2), 1024 * 1024) + 32) & ~31); memcpy (static_cast (data.getData()) + position, buffer, howMany); position += howMany; size = jmax (size, position); } return true; } const void* MemoryOutputStream::getData() const throw() { 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); } const String MemoryOutputStream::toUTF8() const { return String (static_cast (getData()), getDataSize()); } const 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), startPositionInSourceStream (startPositionInSourceStream_), lengthOfSourceStream (lengthOfSourceStream_) { if (deleteSourceWhenDestroyed) sourceToDelete = source; 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() throw() { } 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 throw() { return (value.asInt64 [0] == 0) && (value.asInt64 [1] == 0); } const 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 != 0) 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 (0) { inputStream = file_.inputStream; if (file_.inputSource != 0) { inputStream = streamToDelete = file.inputSource->createInputStream(); } else { #if JUCE_DEBUG file_.numOpenStreams++; #endif } char buffer [30]; if (inputStream != 0 && 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 != 0 && 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 == 0) 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 (0) #if JUCE_DEBUG , numOpenStreams (0) #endif { inputSource = new FileInputSource (file); init(); } ZipFile::ZipFile (InputSource* const inputSource_) : inputStream (0), 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 throw() { return entries.size(); } const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const throw() { ZipEntryInfo* const zei = entries [index]; return zei != 0 ? &(zei->entry) : 0; } int ZipFile::getIndexOfFileName (const String& fileName) const throw() { 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 throw() { return getEntry (getIndexOfFileName (fileName)); } InputStream* ZipFile::createStreamForEntry (const int index) { ZipEntryInfo* const zei = entries[index]; InputStream* stream = 0; if (zei != 0) { 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 != 0) { in = inputSource->createInputStream(); toDelete = in; } if (in != 0) { 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]; zeromem (buffer, sizeof (buffer)); 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 != 0) { 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 != 0) { if (shouldOverwriteFiles && ! targetFile.deleteFile()) return false; if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory()) { ScopedPointer out (targetFile.createOutputStream()); if (out != 0) { out->writeFromInputStream (*in, -1); out = 0; targetFile.setCreationTime (zei->entry.fileTime); targetFile.setLastModificationTime (zei->entry.fileTime); targetFile.setLastAccessTime (zei->entry.fileTime); return true; } } } } } return false; } 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 #include #include #include BEGIN_JUCE_NAMESPACE int CharacterFunctions::length (const char* const s) throw() { return (int) strlen (s); } int CharacterFunctions::length (const juce_wchar* const s) throw() { return (int) wcslen (s); } void CharacterFunctions::copy (char* dest, const char* src, const int maxChars) throw() { strncpy (dest, src, maxChars); } void CharacterFunctions::copy (juce_wchar* dest, const juce_wchar* src, int maxChars) throw() { wcsncpy (dest, src, maxChars); } void CharacterFunctions::copy (juce_wchar* dest, const char* src, const int maxChars) throw() { mbstowcs (dest, src, maxChars); } void CharacterFunctions::copy (char* dest, const juce_wchar* src, const int maxChars) throw() { wcstombs (dest, src, maxChars); } int CharacterFunctions::bytesRequiredForCopy (const juce_wchar* src) throw() { return (int) wcstombs (0, src, 0); } void CharacterFunctions::append (char* dest, const char* src) throw() { strcat (dest, src); } void CharacterFunctions::append (juce_wchar* dest, const juce_wchar* src) throw() { wcscat (dest, src); } int CharacterFunctions::compare (const char* const s1, const char* const s2) throw() { return strcmp (s1, s2); } int CharacterFunctions::compare (const juce_wchar* s1, const juce_wchar* s2) throw() { jassert (s1 != 0 && s2 != 0); return wcscmp (s1, s2); } int CharacterFunctions::compare (const char* const s1, const char* const s2, const int maxChars) throw() { jassert (s1 != 0 && s2 != 0); return strncmp (s1, s2, maxChars); } int CharacterFunctions::compare (const juce_wchar* s1, const juce_wchar* s2, int maxChars) throw() { jassert (s1 != 0 && s2 != 0); return wcsncmp (s1, s2, maxChars); } int CharacterFunctions::compare (const juce_wchar* s1, const char* s2) throw() { jassert (s1 != 0 && s2 != 0); for (;;) { const int diff = (int) (*s1 - (juce_wchar) (unsigned char) *s2); if (diff != 0) return diff; else if (*s1 == 0) break; ++s1; ++s2; } return 0; } int CharacterFunctions::compare (const char* s1, const juce_wchar* s2) throw() { return -compare (s2, s1); } int CharacterFunctions::compareIgnoreCase (const char* const s1, const char* const s2) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return stricmp (s1, s2); #else return strcasecmp (s1, s2); #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const juce_wchar* s2) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return _wcsicmp (s1, s2); #else for (;;) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const char* s2) throw() { jassert (s1 != 0 && s2 != 0); for (;;) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; } int CharacterFunctions::compareIgnoreCase (const char* const s1, const char* const s2, const int maxChars) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return strnicmp (s1, s2, maxChars); #else return strncasecmp (s1, s2, maxChars); #endif } int CharacterFunctions::compareIgnoreCase (const juce_wchar* s1, const juce_wchar* s2, int maxChars) throw() { jassert (s1 != 0 && s2 != 0); #if JUCE_WINDOWS return _wcsnicmp (s1, s2, maxChars); #else while (--maxChars >= 0) { if (*s1 != *s2) { const int diff = toUpperCase (*s1) - toUpperCase (*s2); if (diff != 0) return diff < 0 ? -1 : 1; } else if (*s1 == 0) break; ++s1; ++s2; } return 0; #endif } const char* CharacterFunctions::find (const char* const haystack, const char* const needle) throw() { return strstr (haystack, needle); } const juce_wchar* CharacterFunctions::find (const juce_wchar* haystack, const juce_wchar* const needle) throw() { return wcsstr (haystack, needle); } int CharacterFunctions::indexOfChar (const char* const haystack, const char needle, const bool ignoreCase) throw() { if (haystack != 0) { int i = 0; if (ignoreCase) { const char n1 = toLowerCase (needle); const char n2 = toUpperCase (needle); if (n1 != n2) // if the char is the same in upper/lower case, fall through to the normal search { while (haystack[i] != 0) { if (haystack[i] == n1 || haystack[i] == n2) return i; ++i; } return -1; } jassert (n1 == needle); } while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } } return -1; } int CharacterFunctions::indexOfChar (const juce_wchar* const haystack, const juce_wchar needle, const bool ignoreCase) throw() { if (haystack != 0) { int i = 0; if (ignoreCase) { const juce_wchar n1 = toLowerCase (needle); const juce_wchar n2 = toUpperCase (needle); if (n1 != n2) // if the char is the same in upper/lower case, fall through to the normal search { while (haystack[i] != 0) { if (haystack[i] == n1 || haystack[i] == n2) return i; ++i; } return -1; } jassert (n1 == needle); } while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } } return -1; } int CharacterFunctions::indexOfCharFast (const char* const haystack, const char needle) throw() { jassert (haystack != 0); int i = 0; while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } return -1; } int CharacterFunctions::indexOfCharFast (const juce_wchar* const haystack, const juce_wchar needle) throw() { jassert (haystack != 0); int i = 0; while (haystack[i] != 0) { if (haystack[i] == needle) return i; ++i; } return -1; } int CharacterFunctions::getIntialSectionContainingOnly (const char* const text, const char* const allowedChars) throw() { return allowedChars == 0 ? 0 : (int) strspn (text, allowedChars); } int CharacterFunctions::getIntialSectionContainingOnly (const juce_wchar* const text, const juce_wchar* const allowedChars) throw() { if (allowedChars == 0) return 0; int i = 0; for (;;) { if (indexOfCharFast (allowedChars, text[i]) < 0) break; ++i; } return i; } int CharacterFunctions::ftime (char* const dest, const int maxChars, const char* const format, const struct tm* const tm) throw() { return (int) strftime (dest, maxChars, format, tm); } int CharacterFunctions::ftime (juce_wchar* const dest, const int maxChars, const juce_wchar* const format, const struct tm* const tm) throw() { return (int) wcsftime (dest, maxChars, format, tm); } int CharacterFunctions::getIntValue (const char* const s) throw() { return atoi (s); } int CharacterFunctions::getIntValue (const juce_wchar* s) throw() { #if JUCE_WINDOWS return _wtoi (s); #else int v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const wchar_t c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int) (c - '0'); else break; } return isNeg ? -v : v; #endif } int64 CharacterFunctions::getInt64Value (const char* s) throw() { #if JUCE_LINUX return atoll (s); #elif JUCE_WINDOWS return _atoi64 (s); #else int64 v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const char c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int64) (c - '0'); else break; } return isNeg ? -v : v; #endif } int64 CharacterFunctions::getInt64Value (const juce_wchar* s) throw() { #if JUCE_WINDOWS return _wtoi64 (s); #else int64 v = 0; while (isWhitespace (*s)) ++s; const bool isNeg = *s == '-'; if (isNeg) ++s; for (;;) { const juce_wchar c = *s++; if (c >= '0' && c <= '9') v = v * 10 + (int64) (c - '0'); else break; } return isNeg ? -v : v; #endif } namespace { double juce_mulexp10 (const double value, int exponent) throw() { 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); } template double juce_atof (const CharType* const original) throw() { double result[3] = { 0, 0, 0 }, accumulator[2] = { 0, 0 }; int exponentAdjustment[2] = { 0, 0 }, exponentAccumulator[2] = { -1, -1 }; int exponent = 0, decPointIndex = 0, digit = 0; int lastDigit = 0, numSignificantDigits = 0; bool isNegative = false, digitsFound = false; const int maxSignificantDigits = 15 + 2; const CharType* s = original; while (CharacterFunctions::isWhitespace (*s)) ++s; switch (*s) { case '-': isNegative = true; // fall-through.. case '+': ++s; } if (*s == 'n' || *s == 'N' || *s == 'i' || *s == 'I') return atof (String (original).toUTF8()); // Let the c library deal with NAN and INF for (;;) { if (CharacterFunctions::isDigit (*s)) { lastDigit = digit; digit = *s++ - '0'; digitsFound = true; if (decPointIndex != 0) exponentAdjustment[1]++; if (numSignificantDigits == 0 && digit == 0) continue; if (++numSignificantDigits > maxSignificantDigits) { if (digit > 5) ++accumulator [decPointIndex]; else if (digit == 5 && (lastDigit & 1) != 0) ++accumulator [decPointIndex]; if (decPointIndex > 0) exponentAdjustment[1]--; else exponentAdjustment[0]++; while (CharacterFunctions::isDigit (*s)) { ++s; if (decPointIndex == 0) exponentAdjustment[0]++; } } else { const double maxAccumulatorValue = (double) ((std::numeric_limits::max() - 9) / 10); if (accumulator [decPointIndex] > maxAccumulatorValue) { result [decPointIndex] = juce_mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex]) + accumulator [decPointIndex]; accumulator [decPointIndex] = 0; exponentAccumulator [decPointIndex] = 0; } accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit; exponentAccumulator [decPointIndex]++; } } else if (decPointIndex == 0 && *s == '.') { ++s; decPointIndex = 1; if (numSignificantDigits > maxSignificantDigits) { while (CharacterFunctions::isDigit (*s)) ++s; break; } } else { break; } } result[0] = juce_mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0]; if (decPointIndex != 0) result[1] = juce_mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1]; if ((*s == 'e' || *s == 'E') && digitsFound) { bool negativeExponent = false; switch (*++s) { case '-': negativeExponent = true; // fall-through.. case '+': ++s; } while (CharacterFunctions::isDigit (*s)) exponent = (exponent * 10) + (*s++ - '0'); if (negativeExponent) exponent = -exponent; } double r = juce_mulexp10 (result[0], exponent + exponentAdjustment[0]); if (decPointIndex != 0) r += juce_mulexp10 (result[1], exponent - exponentAdjustment[1]); return isNegative ? -r : r; } } double CharacterFunctions::getDoubleValue (const char* const s) throw() { return juce_atof (s); } double CharacterFunctions::getDoubleValue (const juce_wchar* const s) throw() { return juce_atof (s); } char CharacterFunctions::toUpperCase (const char character) throw() { return (char) toupper (character); } juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) throw() { return towupper (character); } void CharacterFunctions::toUpperCase (char* s) throw() { #if JUCE_WINDOWS strupr (s); #else while (*s != 0) { *s = toUpperCase (*s); ++s; } #endif } void CharacterFunctions::toUpperCase (juce_wchar* s) throw() { #if JUCE_WINDOWS _wcsupr (s); #else while (*s != 0) { *s = toUpperCase (*s); ++s; } #endif } bool CharacterFunctions::isUpperCase (const char character) throw() { return isupper (character) != 0; } bool CharacterFunctions::isUpperCase (const juce_wchar character) throw() { #if JUCE_WINDOWS return iswupper (character) != 0; #else return toLowerCase (character) != character; #endif } char CharacterFunctions::toLowerCase (const char character) throw() { return (char) tolower (character); } juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) throw() { return towlower (character); } void CharacterFunctions::toLowerCase (char* s) throw() { #if JUCE_WINDOWS strlwr (s); #else while (*s != 0) { *s = toLowerCase (*s); ++s; } #endif } void CharacterFunctions::toLowerCase (juce_wchar* s) throw() { #if JUCE_WINDOWS _wcslwr (s); #else while (*s != 0) { *s = toLowerCase (*s); ++s; } #endif } bool CharacterFunctions::isLowerCase (const char character) throw() { return islower (character) != 0; } bool CharacterFunctions::isLowerCase (const juce_wchar character) throw() { #if JUCE_WINDOWS return iswlower (character) != 0; #else return toUpperCase (character) != character; #endif } bool CharacterFunctions::isWhitespace (const char character) throw() { return character == ' ' || (character <= 13 && character >= 9); } bool CharacterFunctions::isWhitespace (const juce_wchar character) throw() { return iswspace (character) != 0; } bool CharacterFunctions::isDigit (const char character) throw() { return (character >= '0' && character <= '9'); } bool CharacterFunctions::isDigit (const juce_wchar character) throw() { return iswdigit (character) != 0; } bool CharacterFunctions::isLetter (const char character) throw() { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z'); } bool CharacterFunctions::isLetter (const juce_wchar character) throw() { return iswalpha (character) != 0; } bool CharacterFunctions::isLetterOrDigit (const char character) throw() { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9'); } bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) throw() { return iswalnum (character) != 0; } int CharacterFunctions::getHexDigitValue (const juce_wchar digit) throw() { 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; } #if JUCE_MSVC #pragma warning (pop) #endif 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() { } const String LocalisedStrings::translate (const String& text) const { return translations.getValue (text, text); } namespace { CriticalSection currentMappingsLock; LocalisedStrings* currentMappings = 0; int findCloseQuote (const String& text, int startPos) { juce_wchar lastChar = 0; for (;;) { const juce_wchar c = text [startPos]; if (c == 0 || (c == '"' && lastChar != '\\')) break; lastChar = c; ++startPos; } return startPos; } const 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 ScopedLock sl (currentMappingsLock); delete currentMappings; currentMappings = newTranslations; } LocalisedStrings* LocalisedStrings::getCurrentMappings() { return currentMappings; } const String LocalisedStrings::translateWithCurrentMappings (const String& text) { const ScopedLock sl (currentMappingsLock); if (currentMappings != 0) return currentMappings->translate (text); return text; } const 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) #endif #include BEGIN_JUCE_NAMESPACE #if JUCE_MSVC #pragma warning (pop) #endif #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 NewLine newLine; class StringHolder { public: StringHolder() : refCount (0x3fffffff), allocatedNumChars (0) { text[0] = 0; } static juce_wchar* createUninitialised (const size_t numChars) { StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) + numChars * sizeof (juce_wchar)]); s->refCount.value = 0; s->allocatedNumChars = numChars; return &(s->text[0]); } static juce_wchar* createCopy (const juce_wchar* const src, const size_t numChars) { juce_wchar* const dest = createUninitialised (numChars); copyChars (dest, src, numChars); return dest; } static juce_wchar* createCopy (const char* const src, const size_t numChars) { juce_wchar* const dest = createUninitialised (numChars); CharacterFunctions::copy (dest, src, (int) numChars); dest [numChars] = 0; return dest; } static inline juce_wchar* getEmpty() throw() { return &(empty.text[0]); } static void retain (juce_wchar* const text) throw() { ++(bufferFromText (text)->refCount); } static inline void release (StringHolder* const b) throw() { if (--(b->refCount) == -1 && b != &empty) delete[] reinterpret_cast (b); } static void release (juce_wchar* const text) throw() { release (bufferFromText (text)); } static juce_wchar* makeUnique (juce_wchar* const text) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0) return text; juce_wchar* const newText = createCopy (text, b->allocatedNumChars); release (b); return newText; } static juce_wchar* makeUniqueWithSize (juce_wchar* const text, size_t numChars) { StringHolder* const b = bufferFromText (text); if (b->refCount.get() <= 0 && b->allocatedNumChars >= numChars) return text; juce_wchar* const newText = createUninitialised (jmax (b->allocatedNumChars, numChars)); copyChars (newText, text, b->allocatedNumChars); release (b); return newText; } static size_t getAllocatedNumChars (juce_wchar* const text) throw() { return bufferFromText (text)->allocatedNumChars; } static void copyChars (juce_wchar* const dest, const juce_wchar* const src, const size_t numChars) throw() { jassert (src != 0 && dest != 0); memcpy (dest, src, numChars * sizeof (juce_wchar)); dest [numChars] = 0; } Atomic refCount; size_t allocatedNumChars; juce_wchar text[1]; static StringHolder empty; private: static inline StringHolder* bufferFromText (void* const text) throw() { // (Can't use offsetof() here because of warnings about this not being a POD) return reinterpret_cast (static_cast (text) - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); } }; StringHolder StringHolder::empty; const String String::empty; void String::createInternal (const juce_wchar* const t, const size_t numChars) { jassert (t[numChars] == 0); // must have a null terminator text = StringHolder::createCopy (t, numChars); } void String::appendInternal (const juce_wchar* const newText, const int numExtraChars) { if (numExtraChars > 0) { const int oldLen = length(); const int newTotalLen = oldLen + numExtraChars; text = StringHolder::makeUniqueWithSize (text, newTotalLen); StringHolder::copyChars (text + oldLen, newText, numExtraChars); } } void String::preallocateStorage (const size_t numChars) { text = StringHolder::makeUniqueWithSize (text, numChars); } String::String() throw() : text (StringHolder::getEmpty()) { } String::~String() throw() { StringHolder::release (text); } String::String (const String& other) throw() : text (other.text) { StringHolder::retain (text); } void String::swapWith (String& other) throw() { swapVariables (text, other.text); } String& String::operator= (const String& other) throw() { juce_wchar* const newText = other.text; StringHolder::retain (newText); StringHolder::release (reinterpret_cast &> (text).exchange (newText)); return *this; } inline String::Preallocation::Preallocation (const size_t numChars_) : numChars (numChars_) {} String::String (const Preallocation& preallocationSize) : text (StringHolder::createUninitialised (preallocationSize.numChars)) { } String::String (const String& stringToCopy, const size_t charsToAllocate) { const size_t otherSize = StringHolder::getAllocatedNumChars (stringToCopy.text); text = StringHolder::createUninitialised (jmax (charsToAllocate, otherSize)); StringHolder::copyChars (text, stringToCopy.text, otherSize); } String::String (const char* const t) { if (t != 0 && *t != 0) text = StringHolder::createCopy (t, CharacterFunctions::length (t)); else text = StringHolder::getEmpty(); } String::String (const juce_wchar* const t) { if (t != 0 && *t != 0) text = StringHolder::createCopy (t, CharacterFunctions::length (t)); else text = StringHolder::getEmpty(); } String::String (const char* const t, const size_t maxChars) { int i; for (i = 0; (size_t) i < maxChars; ++i) if (t[i] == 0) break; if (i > 0) text = StringHolder::createCopy (t, i); else text = StringHolder::getEmpty(); } String::String (const juce_wchar* const t, const size_t maxChars) { int i; for (i = 0; (size_t) i < maxChars; ++i) if (t[i] == 0) break; if (i > 0) text = StringHolder::createCopy (t, i); else text = StringHolder::getEmpty(); } const String String::charToString (const juce_wchar character) { String result (Preallocation (1)); result.text[0] = character; result.text[1] = 0; return result; } namespace NumberToStringConverters { // pass in a pointer to the END of a buffer.. juce_wchar* int64ToString (juce_wchar* t, const int64 n) throw() { *--t = 0; int64 v = (n >= 0) ? n : -n; do { *--t = (juce_wchar) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } juce_wchar* uint64ToString (juce_wchar* t, int64 v) throw() { *--t = 0; do { *--t = (juce_wchar) ('0' + (int) (v % 10)); v /= 10; } while (v > 0); return t; } juce_wchar* intToString (juce_wchar* t, const int n) throw() { if (n == (int) 0x80000000) // (would cause an overflow) return int64ToString (t, n); *--t = 0; int v = abs (n); do { *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; } while (v > 0); if (n < 0) *--t = '-'; return t; } juce_wchar* uintToString (juce_wchar* t, unsigned int v) throw() { *--t = 0; do { *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; } while (v > 0); return t; } juce_wchar getDecimalPoint() { #if JUCE_VC7_OR_EARLIER static juce_wchar dp = std::_USE (std::locale(), std::numpunct ).decimal_point(); #else static juce_wchar dp = std::use_facet > (std::locale()).decimal_point(); #endif return dp; } juce_wchar* doubleToString (juce_wchar* buffer, int numChars, double n, int numDecPlaces, size_t& len) throw() { if (numDecPlaces > 0 && n > -1.0e20 && n < 1.0e20) { juce_wchar* const end = buffer + numChars; juce_wchar* t = end; int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); *--t = (juce_wchar) 0; while (numDecPlaces >= 0 || v > 0) { if (numDecPlaces == 0) *--t = getDecimalPoint(); *--t = (juce_wchar) ('0' + (v % 10)); v /= 10; --numDecPlaces; } if (n < 0) *--t = '-'; len = end - t - 1; return t; } else { #if JUCE_WINDOWS #if JUCE_VC7_OR_EARLIER || JUCE_MINGW len = _snwprintf (buffer, numChars, L"%.9g", n); #else len = _snwprintf_s (buffer, numChars, _TRUNCATE, L"%.9g", n); #endif #else len = swprintf (buffer, numChars, L"%.9g", n); #endif return buffer; } } } String::String (const int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, number); createInternal (start, end - start - 1); } String::String (const unsigned int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, number); createInternal (start, end - start - 1); } String::String (const short number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, (int) number); createInternal (start, end - start - 1); } String::String (const unsigned short number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, (unsigned int) number); createInternal (start, end - start - 1); } String::String (const int64 number) { juce_wchar buffer [32]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::int64ToString (end, number); createInternal (start, end - start - 1); } String::String (const uint64 number) { juce_wchar buffer [32]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uint64ToString (end, number); createInternal (start, end - start - 1); } String::String (const float number, const int numberOfDecimalPlaces) { juce_wchar buffer [48]; size_t len; juce_wchar* start = NumberToStringConverters::doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); createInternal (start, len); } String::String (const double number, const int numberOfDecimalPlaces) { juce_wchar buffer [48]; size_t len; juce_wchar* start = NumberToStringConverters::doubleToString (buffer, numElementsInArray (buffer), number, numberOfDecimalPlaces, len); createInternal (start, len); } int String::length() const throw() { return CharacterFunctions::length (text); } int String::hashCode() const throw() { const juce_wchar* t = text; int result = 0; while (*t != (juce_wchar) 0) result = 31 * result + *t++; return result; } int64 String::hashCode64() const throw() { const juce_wchar* t = text; int64 result = 0; while (*t != (juce_wchar) 0) result = 101 * result + *t++; return result; } JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) throw() { return string1.compare (string2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const char* string2) throw() { return string1.compare (string2) == 0; } JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const juce_wchar* string2) throw() { return string1.compare (string2) == 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const String& string2) throw() { return string1.compare (string2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const char* string2) throw() { return string1.compare (string2) != 0; } JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const juce_wchar* string2) throw() { return string1.compare (string2) != 0; } JUCE_API bool JUCE_CALLTYPE operator> (const String& string1, const String& string2) throw() { return string1.compare (string2) > 0; } JUCE_API bool JUCE_CALLTYPE operator< (const String& string1, const String& string2) throw() { return string1.compare (string2) < 0; } JUCE_API bool JUCE_CALLTYPE operator>= (const String& string1, const String& string2) throw() { return string1.compare (string2) >= 0; } JUCE_API bool JUCE_CALLTYPE operator<= (const String& string1, const String& string2) throw() { return string1.compare (string2) <= 0; } bool String::equalsIgnoreCase (const juce_wchar* t) const throw() { return t != 0 ? CharacterFunctions::compareIgnoreCase (text, t) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const char* t) const throw() { return t != 0 ? CharacterFunctions::compareIgnoreCase (text, t) == 0 : isEmpty(); } bool String::equalsIgnoreCase (const String& other) const throw() { return text == other.text || CharacterFunctions::compareIgnoreCase (text, other.text) == 0; } int String::compare (const String& other) const throw() { return (text == other.text) ? 0 : CharacterFunctions::compare (text, other.text); } int String::compare (const char* other) const throw() { return other == 0 ? isEmpty() : CharacterFunctions::compare (text, other); } int String::compare (const juce_wchar* other) const throw() { return other == 0 ? isEmpty() : CharacterFunctions::compare (text, other); } int String::compareIgnoreCase (const String& other) const throw() { return (text == other.text) ? 0 : CharacterFunctions::compareIgnoreCase (text, other.text); } int String::compareLexicographically (const String& other) const throw() { const juce_wchar* s1 = text; while (*s1 != 0 && ! CharacterFunctions::isLetterOrDigit (*s1)) ++s1; const juce_wchar* s2 = other.text; while (*s2 != 0 && ! CharacterFunctions::isLetterOrDigit (*s2)) ++s2; return CharacterFunctions::compareIgnoreCase (s1, s2); } String& String::operator+= (const juce_wchar* const t) { if (t != 0) appendInternal (t, CharacterFunctions::length (t)); return *this; } String& String::operator+= (const String& other) { if (isEmpty()) operator= (other); else appendInternal (other.text, other.length()); return *this; } String& String::operator+= (const char ch) { const juce_wchar asString[] = { (juce_wchar) ch, 0 }; return operator+= (static_cast (asString)); } String& String::operator+= (const juce_wchar ch) { const juce_wchar asString[] = { (juce_wchar) ch, 0 }; return operator+= (static_cast (asString)); } String& String::operator+= (const int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::intToString (end, number); appendInternal (start, (int) (end - start)); return *this; } String& String::operator+= (const unsigned int number) { juce_wchar buffer [16]; juce_wchar* const end = buffer + numElementsInArray (buffer); juce_wchar* const start = NumberToStringConverters::uintToString (end, number); appendInternal (start, (int) (end - start)); return *this; } void String::append (const juce_wchar* const other, const int howMany) { if (howMany > 0) { int i; for (i = 0; i < howMany; ++i) if (other[i] == 0) break; appendInternal (other, i); } } JUCE_API const String JUCE_CALLTYPE operator+ (const char* const string1, const String& string2) { String s (string1); return s += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (const juce_wchar* const string1, const String& string2) { String s (string1); return s += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (const char string1, const String& string2) { return String::charToString (string1) + string2; } JUCE_API const String JUCE_CALLTYPE operator+ (const juce_wchar string1, const String& string2) { return String::charToString (string1) + string2; } JUCE_API const String JUCE_CALLTYPE operator+ (String string1, const String& string2) { return string1 += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (String string1, const char* const string2) { return string1 += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (String string1, const juce_wchar* const string2) { return string1 += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (String string1, const char string2) { return string1 += string2; } JUCE_API const String JUCE_CALLTYPE operator+ (String string1, const juce_wchar string2) { return string1 += string2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char characterToAppend) { return string1 += characterToAppend; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const juce_wchar characterToAppend) { return string1 += characterToAppend; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char* const string2) { return string1 += string2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const juce_wchar* const string2) { return string1 += string2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string2) { return string1 += string2; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const short number) { return string1 += (int) number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const int number) { return string1 += number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const unsigned int number) { return string1 += number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const long number) { return string1 += (int) number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const unsigned long number) { return string1 += (unsigned int) number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const float number) { return string1 += String (number); } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const double number) { return string1 += String (number); } JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, 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(); HeapBlock temp (numBytes + 1); text.copyToUTF8 (temp, numBytes + 1); stream.write (temp, numBytes); return stream; } JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&) { return string1 += NewLine::getDefault(); } int String::indexOfChar (const juce_wchar character) const throw() { const juce_wchar* t = text; for (;;) { if (*t == character) return (int) (t - text); if (*t++ == 0) return -1; } } int String::lastIndexOfChar (const juce_wchar character) const throw() { for (int i = length(); --i >= 0;) if (text[i] == character) return i; return -1; } int String::indexOf (const String& t) const throw() { const juce_wchar* const r = CharacterFunctions::find (text, t.text); return r == 0 ? -1 : (int) (r - text); } int String::indexOfChar (const int startIndex, const juce_wchar character) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* t = text + jmax (0, startIndex); for (;;) { if (*t == character) return (int) (t - text); if (*t == 0) return -1; ++t; } } int String::indexOfAnyOf (const String& charactersToLookFor, const int startIndex, const bool ignoreCase) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* t = text + jmax (0, startIndex); while (*t != 0) { if (CharacterFunctions::indexOfChar (charactersToLookFor.text, *t, ignoreCase) >= 0) return (int) (t - text); ++t; } return -1; } int String::indexOf (const int startIndex, const String& other) const throw() { if (startIndex > 0 && startIndex >= length()) return -1; const juce_wchar* const found = CharacterFunctions::find (text + jmax (0, startIndex), other.text); return found == 0 ? -1 : (int) (found - text); } int String::indexOfIgnoreCase (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); const int end = length() - len; for (int i = 0; i <= end; ++i) if (CharacterFunctions::compareIgnoreCase (text + i, other.text, len) == 0) return i; } return -1; } int String::indexOfIgnoreCase (const int startIndex, const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); const int end = length() - len; for (int i = jmax (0, startIndex); i <= end; ++i) if (CharacterFunctions::compareIgnoreCase (text + i, other.text, len) == 0) return i; } return -1; } int String::lastIndexOf (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { const juce_wchar* n = text + i; while (i >= 0) { if (CharacterFunctions::compare (n--, other.text, len) == 0) return i; --i; } } } return -1; } int String::lastIndexOfIgnoreCase (const String& other) const throw() { if (other.isNotEmpty()) { const int len = other.length(); int i = length() - len; if (i >= 0) { const juce_wchar* n = text + i; while (i >= 0) { if (CharacterFunctions::compareIgnoreCase (n--, other.text, len) == 0) return i; --i; } } } return -1; } int String::lastIndexOfAnyOf (const String& charactersToLookFor, const bool ignoreCase) const throw() { for (int i = length(); --i >= 0;) if (CharacterFunctions::indexOfChar (charactersToLookFor.text, text[i], ignoreCase) >= 0) return i; return -1; } bool String::contains (const String& other) const throw() { return indexOf (other) >= 0; } bool String::containsChar (const juce_wchar character) const throw() { const juce_wchar* t = text; for (;;) { if (*t == 0) return false; if (*t == character) return true; ++t; } } bool String::containsIgnoreCase (const String& t) const throw() { return indexOfIgnoreCase (t) >= 0; } int String::indexOfWholeWord (const String& word) const throw() { if (word.isNotEmpty()) { const int wordLen = word.length(); const int end = length() - wordLen; const juce_wchar* t = text; for (int i = 0; i <= end; ++i) { if (CharacterFunctions::compare (t, word.text, wordLen) == 0 && (i == 0 || ! CharacterFunctions::isLetterOrDigit (* (t - 1))) && ! CharacterFunctions::isLetterOrDigit (t [wordLen])) { return i; } ++t; } } return -1; } int String::indexOfWholeWordIgnoreCase (const String& word) const throw() { if (word.isNotEmpty()) { const int wordLen = word.length(); const int end = length() - wordLen; const juce_wchar* t = text; for (int i = 0; i <= end; ++i) { if (CharacterFunctions::compareIgnoreCase (t, word.text, wordLen) == 0 && (i == 0 || ! CharacterFunctions::isLetterOrDigit (* (t - 1))) && ! CharacterFunctions::isLetterOrDigit (t [wordLen])) { return i; } ++t; } } return -1; } bool String::containsWholeWord (const String& wordToLookFor) const throw() { return indexOfWholeWord (wordToLookFor) >= 0; } bool String::containsWholeWordIgnoreCase (const String& wordToLookFor) const throw() { return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; } namespace WildCardHelpers { int indexOfMatch (const juce_wchar* const wildcard, const juce_wchar* const test, const bool ignoreCase) throw() { int start = 0; while (test [start] != 0) { int i = 0; for (;;) { const juce_wchar wc = wildcard [i]; const juce_wchar c = test [i + start]; if (wc == c || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (c)) || (wc == '?' && c != 0)) { if (wc == 0) return start; ++i; } else { if (wc == '*' && (wildcard [i + 1] == 0 || indexOfMatch (wildcard + i + 1, test + start + i, ignoreCase) >= 0)) { return start; } break; } } ++start; } return -1; } } bool String::matchesWildcard (const String& wildcard, const bool ignoreCase) const throw() { int i = 0; for (;;) { const juce_wchar wc = wildcard.text [i]; const juce_wchar c = text [i]; if (wc == c || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (c)) || (wc == '?' && c != 0)) { if (wc == 0) return true; ++i; } else { return wc == '*' && (wildcard [i + 1] == 0 || WildCardHelpers::indexOfMatch (wildcard.text + i + 1, text + i, ignoreCase) >= 0); } } } const String String::repeatedString (const String& stringToRepeat, int numberOfTimesToRepeat) { const int len = stringToRepeat.length(); String result (Preallocation (len * numberOfTimesToRepeat + 1)); juce_wchar* n = result.text; *n = 0; while (--numberOfTimesToRepeat >= 0) { StringHolder::copyChars (n, stringToRepeat.text, len); n += len; } return result; } const String String::paddedLeft (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); const int len = length(); if (len >= minimumLength || padCharacter == 0) return *this; String result (Preallocation (minimumLength + 1)); juce_wchar* n = result.text; minimumLength -= len; while (--minimumLength >= 0) *n++ = padCharacter; StringHolder::copyChars (n, text, len); return result; } const String String::paddedRight (const juce_wchar padCharacter, int minimumLength) const { jassert (padCharacter != 0); const int len = length(); if (len >= minimumLength || padCharacter == 0) return *this; String result (*this, (size_t) minimumLength); juce_wchar* n = result.text + len; minimumLength -= len; while (--minimumLength >= 0) *n++ = padCharacter; *n = 0; return result; } const 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; } const int len = length(); if (index + numCharsToReplace > len) { if (index > len) { // replacing beyond the end of the string? index = len; jassertfalse; } numCharsToReplace = len - index; } const int newStringLen = stringToInsert.length(); const int newTotalLen = len + newStringLen - numCharsToReplace; if (newTotalLen <= 0) return String::empty; String result (Preallocation ((size_t) newTotalLen)); StringHolder::copyChars (result.text, text, index); if (newStringLen > 0) StringHolder::copyChars (result.text + index, stringToInsert.text, newStringLen); const int endStringLen = newTotalLen - (index + newStringLen); if (endStringLen > 0) StringHolder::copyChars (result.text + (index + newStringLen), text + (index + numCharsToReplace), endStringLen); return result; } const 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; } const String String::replaceCharacter (const juce_wchar charToReplace, const juce_wchar charToInsert) const { const int index = indexOfChar (charToReplace); if (index < 0) return *this; String result (*this, size_t()); juce_wchar* t = result.text + index; while (*t != 0) { if (*t == charToReplace) *t = charToInsert; ++t; } return result; } const String String::replaceCharacters (const String& charactersToReplace, const String& charactersToInsertInstead) const { String result (*this, size_t()); juce_wchar* t = result.text; const int len2 = charactersToInsertInstead.length(); // the two strings passed in are supposed to be the same length! jassert (len2 == charactersToReplace.length()); while (*t != 0) { const int index = charactersToReplace.indexOfChar (*t); if (isPositiveAndBelow (index, len2)) *t = charactersToInsertInstead [index]; ++t; } return result; } bool String::startsWith (const String& other) const throw() { return CharacterFunctions::compare (text, other.text, other.length()) == 0; } bool String::startsWithIgnoreCase (const String& other) const throw() { return CharacterFunctions::compareIgnoreCase (text, other.text, other.length()) == 0; } bool String::startsWithChar (const juce_wchar character) const throw() { jassert (character != 0); // strings can't contain a null character! return text[0] == character; } bool String::endsWithChar (const juce_wchar character) const throw() { jassert (character != 0); // strings can't contain a null character! return text[0] != 0 && text [length() - 1] == character; } bool String::endsWith (const String& other) const throw() { const int thisLen = length(); const int otherLen = other.length(); return thisLen >= otherLen && CharacterFunctions::compare (text + thisLen - otherLen, other.text) == 0; } bool String::endsWithIgnoreCase (const String& other) const throw() { const int thisLen = length(); const int otherLen = other.length(); return thisLen >= otherLen && CharacterFunctions::compareIgnoreCase (text + thisLen - otherLen, other.text) == 0; } const String String::toUpperCase() const { String result (*this, size_t()); CharacterFunctions::toUpperCase (result.text); return result; } const String String::toLowerCase() const { String result (*this, size_t()); CharacterFunctions::toLowerCase (result.text); return result; } juce_wchar& String::operator[] (const int index) { jassert (isPositiveAndNotGreaterThan (index, length())); text = StringHolder::makeUnique (text); return text [index]; } juce_wchar String::getLastCharacter() const throw() { return isEmpty() ? juce_wchar() : text [length() - 1]; } const String String::substring (int start, int end) const { if (start < 0) start = 0; else if (end <= start) return empty; int len = 0; while (len <= end && text [len] != 0) ++len; if (end >= len) { if (start == 0) return *this; end = len; } return String (text + start, end - start); } const String String::substring (const int start) const { if (start <= 0) return *this; const int len = length(); if (start >= len) return empty; return String (text + start, len - start); } const String String::dropLastCharacters (const int numberToDrop) const { return String (text, jmax (0, length() - numberToDrop)); } const String String::getLastCharacters (const int numCharacters) const { return String (text + jmax (0, length() - jmax (0, numCharacters))); } const 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()); } const 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()); } const 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); } const 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] == '\''; } const String String::unquoted() const { String s (*this); if (s.text[0] == '"' || s.text[0] == '\'') s = s.substring (1); const int lastCharIndex = s.length() - 1; if (lastCharIndex >= 0 && (s [lastCharIndex] == '"' || s[lastCharIndex] == '\'')) s [lastCharIndex] = 0; return s; } const 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; } const String String::trim() const { if (isEmpty()) return empty; int start = 0; while (CharacterFunctions::isWhitespace (text [start])) ++start; const int len = length(); int end = len - 1; while ((end >= start) && CharacterFunctions::isWhitespace (text [end])) --end; ++end; if (end <= start) return empty; else if (start > 0 || end < len) return String (text + start, end - start); return *this; } const String String::trimStart() const { if (isEmpty()) return empty; const juce_wchar* t = text; while (CharacterFunctions::isWhitespace (*t)) ++t; if (t == text) return *this; return String (t); } const String String::trimEnd() const { if (isEmpty()) return empty; const juce_wchar* endT = text + (length() - 1); while ((endT >= text) && CharacterFunctions::isWhitespace (*endT)) --endT; return String (text, (int) (++endT - text)); } const String String::trimCharactersAtStart (const String& charactersToTrim) const { const juce_wchar* t = text; while (charactersToTrim.containsChar (*t)) ++t; return t == text ? *this : String (t); } const String String::trimCharactersAtEnd (const String& charactersToTrim) const { if (isEmpty()) return empty; const int len = length(); const juce_wchar* endT = text + (len - 1); int numToRemove = 0; while (numToRemove < len && charactersToTrim.containsChar (*endT)) { ++numToRemove; --endT; } return numToRemove > 0 ? String (text, len - numToRemove) : *this; } const String String::retainCharacters (const String& charactersToRetain) const { if (isEmpty()) return empty; String result (Preallocation (StringHolder::getAllocatedNumChars (text))); juce_wchar* dst = result.text; const juce_wchar* src = text; while (*src != 0) { if (charactersToRetain.containsChar (*src)) *dst++ = *src; ++src; } *dst = 0; return result; } const String String::removeCharacters (const String& charactersToRemove) const { if (isEmpty()) return empty; String result (Preallocation (StringHolder::getAllocatedNumChars (text))); juce_wchar* dst = result.text; const juce_wchar* src = text; while (*src != 0) { if (! charactersToRemove.containsChar (*src)) *dst++ = *src; ++src; } *dst = 0; return result; } const String String::initialSectionContainingOnly (const String& permittedCharacters) const { int i = 0; for (;;) { if (! permittedCharacters.containsChar (text[i])) break; ++i; } return substring (0, i); } const String String::initialSectionNotContaining (const String& charactersToStopAt) const { const juce_wchar* const t = text; int i = 0; while (t[i] != 0) { if (charactersToStopAt.containsChar (t[i])) return String (text, i); ++i; } return empty; } bool String::containsOnly (const String& chars) const throw() { const juce_wchar* t = text; while (*t != 0) if (! chars.containsChar (*t++)) return false; return true; } bool String::containsAnyOf (const String& chars) const throw() { const juce_wchar* t = text; while (*t != 0) if (chars.containsChar (*t++)) return true; return false; } bool String::containsNonWhitespaceChars() const throw() { const juce_wchar* t = text; while (*t != 0) if (! CharacterFunctions::isWhitespace (*t++)) return true; return false; } const String String::formatted (const juce_wchar* const pf, ... ) { jassert (pf != 0); va_list args; va_start (args, pf); size_t bufferSize = 256; String result (Preallocation ((size_t) bufferSize)); result.text[0] = 0; for (;;) { #if JUCE_LINUX && JUCE_64BIT va_list tempArgs; va_copy (tempArgs, args); const int num = (int) vswprintf (result.text, bufferSize - 1, pf, tempArgs); va_end (tempArgs); #elif JUCE_WINDOWS #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4996) #endif const int num = (int) _vsnwprintf (result.text, bufferSize - 1, pf, args); #if JUCE_MSVC #pragma warning (pop) #endif #else const int num = (int) vswprintf (result.text, bufferSize - 1, pf, args); #endif if (num > 0) return result; 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. result.preallocateStorage (bufferSize); } return empty; } int String::getIntValue() const throw() { return CharacterFunctions::getIntValue (text); } int String::getTrailingIntValue() const throw() { int n = 0; int mult = 1; const juce_wchar* t = text + length(); while (--t >= text) { const juce_wchar c = *t; if (! CharacterFunctions::isDigit (c)) { if (c == '-') n = -n; break; } n += mult * (c - '0'); mult *= 10; } return n; } int64 String::getLargeIntValue() const throw() { return CharacterFunctions::getInt64Value (text); } float String::getFloatValue() const throw() { return (float) CharacterFunctions::getDoubleValue (text); } double String::getDoubleValue() const throw() { return CharacterFunctions::getDoubleValue (text); } static const juce_wchar* const hexDigits = JUCE_T("0123456789abcdef"); const String String::toHexString (const int number) { juce_wchar buffer[32]; juce_wchar* const end = buffer + 32; juce_wchar* t = end; *--t = 0; unsigned int v = (unsigned int) number; do { *--t = hexDigits [v & 15]; v >>= 4; } while (v != 0); return String (t, (int) (((char*) end) - (char*) t) - 1); } const String String::toHexString (const int64 number) { juce_wchar buffer[32]; juce_wchar* const end = buffer + 32; juce_wchar* t = end; *--t = 0; uint64 v = (uint64) number; do { *--t = hexDigits [(int) (v & 15)]; v >>= 4; } while (v != 0); return String (t, (int) (((char*) end) - (char*) t)); } const String String::toHexString (const short number) { return toHexString ((int) (unsigned short) number); } const String String::toHexString (const unsigned char* data, const int size, const int groupSize) { if (size <= 0) return empty; int numChars = (size * 2) + 2; if (groupSize > 0) numChars += size / groupSize; String s (Preallocation ((size_t) numChars)); juce_wchar* d = s.text; for (int i = 0; i < size; ++i) { *d++ = hexDigits [(*data) >> 4]; *d++ = hexDigits [(*data) & 0xf]; ++data; if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) *d++ = ' '; } *d = 0; return s; } int String::getHexValue32() const throw() { int result = 0; const juce_wchar* c = text; for (;;) { const int hexValue = CharacterFunctions::getHexDigitValue (*c); if (hexValue >= 0) result = (result << 4) | hexValue; else if (*c == 0) break; ++c; } return result; } int64 String::getHexValue64() const throw() { int64 result = 0; const juce_wchar* c = text; for (;;) { const int hexValue = CharacterFunctions::getHexDigitValue (*c); if (hexValue >= 0) result = (result << 4) | hexValue; else if (*c == 0) break; ++c; } return result; } const String String::createStringFromData (const void* const data_, const int size) { const char* const data = static_cast (data_); if (size <= 0 || data == 0) { return empty; } else if (size < 2) { return charToString (data[0]); } else if ((data[0] == (char)-2 && data[1] == (char)-1) || (data[0] == (char)-1 && data[1] == (char)-2)) { // assume it's 16-bit unicode const bool bigEndian = (data[0] == (char)-2); const int numChars = size / 2 - 1; String result; result.preallocateStorage (numChars + 2); const uint16* const src = (const uint16*) (data + 2); juce_wchar* const dst = const_cast (static_cast (result)); if (bigEndian) { for (int i = 0; i < numChars; ++i) dst[i] = (juce_wchar) ByteOrder::swapIfLittleEndian (src[i]); } else { for (int i = 0; i < numChars; ++i) dst[i] = (juce_wchar) ByteOrder::swapIfBigEndian (src[i]); } dst [numChars] = 0; return result; } else { return String::fromUTF8 (data, size); } } const char* String::toUTF8() const { if (isEmpty()) { return reinterpret_cast (text); } else { const int currentLen = length() + 1; const int utf8BytesNeeded = getNumBytesAsUTF8(); String* const mutableThis = const_cast (this); mutableThis->text = StringHolder::makeUniqueWithSize (mutableThis->text, currentLen + 1 + utf8BytesNeeded / sizeof (juce_wchar)); char* const otherCopy = reinterpret_cast (mutableThis->text + currentLen); #if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) *(juce_wchar*) (otherCopy + (utf8BytesNeeded & ~(sizeof (juce_wchar) - 1))) = 0; #endif copyToUTF8 (otherCopy, std::numeric_limits::max()); return otherCopy; } } int String::copyToUTF8 (char* const buffer, const int maxBufferSizeBytes) const throw() { jassert (maxBufferSizeBytes >= 0); // keep this value positive, or no characters will be copied! int num = 0, index = 0; for (;;) { const uint32 c = (uint32) text [index++]; if (c >= 0x80) { int numExtraBytes = 1; if (c >= 0x800) { ++numExtraBytes; if (c >= 0x10000) { ++numExtraBytes; if (c >= 0x200000) { ++numExtraBytes; if (c >= 0x4000000) ++numExtraBytes; } } } if (buffer != 0) { if (num + numExtraBytes >= maxBufferSizeBytes) { buffer [num++] = 0; break; } else { buffer [num++] = (uint8) ((0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6))); while (--numExtraBytes >= 0) buffer [num++] = (uint8) (0x80 | (0x3f & (c >> (numExtraBytes * 6)))); } } else { num += numExtraBytes + 1; } } else { if (buffer != 0) { if (num + 1 >= maxBufferSizeBytes) { buffer [num++] = 0; break; } buffer [num] = (uint8) c; } ++num; } if (c == 0) break; } return num; } int String::getNumBytesAsUTF8() const throw() { int num = 0; const juce_wchar* t = text; for (;;) { const uint32 c = (uint32) *t; if (c >= 0x80) { ++num; if (c >= 0x800) { ++num; if (c >= 0x10000) { ++num; if (c >= 0x200000) { ++num; if (c >= 0x4000000) ++num; } } } } else if (c == 0) break; ++num; ++t; } return num; } const String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) { if (buffer == 0) return empty; if (bufferSizeBytes < 0) bufferSizeBytes = std::numeric_limits::max(); size_t numBytes; for (numBytes = 0; numBytes < (size_t) bufferSizeBytes; ++numBytes) if (buffer [numBytes] == 0) break; String result (Preallocation (numBytes + 1)); juce_wchar* dest = result.text; size_t i = 0; while (i < numBytes) { const char c = buffer [i++]; if (c < 0) { unsigned int mask = 0x7f; int bit = 0x40; int numExtraValues = 0; while (bit != 0 && (c & bit) != 0) { bit >>= 1; mask >>= 1; ++numExtraValues; } int n = (mask & (unsigned char) c); while (--numExtraValues >= 0 && i < (size_t) bufferSizeBytes) { const char nextByte = buffer[i]; if ((nextByte & 0xc0) != 0x80) break; n <<= 6; n |= (nextByte & 0x3f); ++i; } *dest++ = (juce_wchar) n; } else { *dest++ = (juce_wchar) c; } } *dest = 0; return result; } const char* String::toCString() const { if (isEmpty()) { return reinterpret_cast (text); } else { const int len = length(); String* const mutableThis = const_cast (this); mutableThis->text = StringHolder::makeUniqueWithSize (mutableThis->text, (len + 1) * 2); char* otherCopy = reinterpret_cast (mutableThis->text + len + 1); CharacterFunctions::copy (otherCopy, text, len); otherCopy [len] = 0; return otherCopy; } } #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4996) #endif int String::getNumBytesAsCString() const throw() { return (int) wcstombs (0, text, 0); } int String::copyToCString (char* destBuffer, const int maxBufferSizeBytes) const throw() { const int numBytes = (int) wcstombs (destBuffer, text, maxBufferSizeBytes); if (destBuffer != 0 && numBytes >= 0) destBuffer [numBytes] = 0; return numBytes; } #if JUCE_MSVC #pragma warning (pop) #endif void String::copyToUnicode (juce_wchar* const destBuffer, const int maxCharsToCopy) const throw() { jassert (destBuffer != 0 && maxCharsToCopy >= 0); if (destBuffer != 0 && maxCharsToCopy >= 0) StringHolder::copyChars (destBuffer, text, jmin (maxCharsToCopy, length())); } 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) { result.preallocateStorage (nextIndex + len); s.copyToUnicode (static_cast (result) + nextIndex, len); nextIndex += len; } } #if JUCE_UNIT_TESTS class StringTests : public UnitTest { public: StringTests() : UnitTest ("String class") {} 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")); expect (s1.indexOf (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') && ! 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 (! 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)); expect (s5.fromFirstOccurrenceOf (String::empty, true, false) == s5); expect (s5.fromFirstOccurrenceOf ("xword2", true, false) == s5.substring (100)); expect (s5.fromFirstOccurrenceOf (L"word2", true, false) == s5.substring (5)); expect (s5.fromFirstOccurrenceOf ("Word2", true, true) == s5.substring (5)); expect (s5.fromFirstOccurrenceOf ("word2", false, false) == s5.getLastCharacters (6)); expect (s5.fromFirstOccurrenceOf (L"Word2", false, true) == s5.getLastCharacters (6)); expect (s5.fromLastOccurrenceOf (String::empty, true, false) == s5); expect (s5.fromLastOccurrenceOf (L"wordx", true, false) == s5); expect (s5.fromLastOccurrenceOf ("word", true, false) == s5.getLastCharacters (5)); expect (s5.fromLastOccurrenceOf (L"worD", true, true) == s5.getLastCharacters (5)); expect (s5.fromLastOccurrenceOf ("word", false, false) == s5.getLastCharacters (1)); expect (s5.fromLastOccurrenceOf (L"worD", false, true) == s5.getLastCharacters (1)); expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); expect (s5.upToFirstOccurrenceOf ("word4", true, false) == s5); expect (s5.upToFirstOccurrenceOf (L"word2", true, false) == s5.substring (0, 10)); expect (s5.upToFirstOccurrenceOf ("Word2", true, true) == s5.substring (0, 10)); expect (s5.upToFirstOccurrenceOf (L"word2", false, false) == s5.substring (0, 5)); expect (s5.upToFirstOccurrenceOf ("Word2", false, true) == s5.substring (0, 5)); expect (s5.upToLastOccurrenceOf (String::empty, true, false) == s5); expect (s5.upToLastOccurrenceOf ("zword", true, false) == s5); expect (s5.upToLastOccurrenceOf ("word", true, false) == s5.dropLastCharacters (1)); expect (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false) == s5.dropLastCharacters (1)); expect (s5.upToLastOccurrenceOf ("Word", true, true) == s5.dropLastCharacters (1)); expect (s5.upToLastOccurrenceOf ("word", false, false) == s5.dropLastCharacters (5)); expect (s5.upToLastOccurrenceOf ("Word", false, true) == s5.dropLastCharacters (5)); expect (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"); expect (s5.replace ("Word2", L"xyz", true) == String ("word xyz word3")); expect (s5.replaceCharacter (L'w', 'x') != s5); expect (s5.replaceCharacter ('w', L'x').replaceCharacter ('x', 'w') == s5); expect (s5.replaceCharacters ("wo", "xy") != s5); expect (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", L"wo") == s5); expect (s5.retainCharacters ("1wordxya") == String ("wordwordword")); expect (s5.retainCharacters (String::empty).isEmpty()); expect (s5.removeCharacters ("1wordxya") == " 2 3"); expect (s5.removeCharacters (String::empty) == s5); expect (s5.initialSectionContainingOnly ("word") == L"word"); expect (s5.initialSectionNotContaining (String ("xyz ")) == String ("word")); 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"); expect (s6.trim() == String ("xyz")); expect (s6.trim().trim() == "xyz"); expect (s5.trim() == s5); expect (s6.trimStart().trimEnd() == s6.trim()); expect (s6.trimStart().trimEnd() == s6.trimEnd().trimStart()); expect (s6.trimStart().trimStart().trimEnd().trimEnd() == s6.trimEnd().trimStart()); expect (s6.trimStart() != s6.trimEnd()); expect (("\t\r\n " + s6 + "\t\n \r").trim() == s6.trim()); expect (String::repeatedString ("xyz", 3) == L"xyzxyzxyz"); } { beginTest ("UTF8"); String s ("word word2 word3"); { char buffer [100]; memset (buffer, 0xff, sizeof (buffer)); s.copyToUTF8 (buffer, 100); expect (String::fromUTF8 (buffer, 100) == s); juce_wchar bufferUnicode [100]; memset (bufferUnicode, 0xff, sizeof (bufferUnicode)); s.copyToUnicode (bufferUnicode, 100); expect (String (bufferUnicode, 100) == s); } { juce_wchar wideBuffer [50]; zerostruct (wideBuffer); for (int i = 0; i < numElementsInArray (wideBuffer) - 1; ++i) wideBuffer[i] = (juce_wchar) (1 + Random::getSystemRandom().nextInt (std::numeric_limits::max() - 1)); String wide (wideBuffer); expect (wide == (const juce_wchar*) wideBuffer); expect (wide.length() == numElementsInArray (wideBuffer) - 1); expect (String::fromUTF8 (wide.toUTF8()) == wide); expect (String::fromUTF8 (wide.toUTF8()) == wideBuffer); } } } }; 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() throw() { } StringArray::StringArray (const StringArray& other) : strings (other.strings) { } StringArray::StringArray (const String& firstValue) { strings.add (firstValue); } StringArray::StringArray (const juce_wchar* const* const initialStrings, const int numberOfStrings) { for (int i = 0; i < numberOfStrings; ++i) strings.add (initialStrings [i]); } StringArray::StringArray (const char* const* const initialStrings, const int numberOfStrings) { for (int i = 0; i < numberOfStrings; ++i) strings.add (initialStrings [i]); } StringArray::StringArray (const juce_wchar* const* const initialStrings) { int i = 0; while (initialStrings[i] != 0) strings.add (initialStrings [i++]); } StringArray::StringArray (const char* const* const initialStrings) { int i = 0; while (initialStrings[i] != 0) strings.add (initialStrings [i++]); } StringArray& StringArray::operator= (const StringArray& other) { strings = other.strings; return *this; } StringArray::~StringArray() { } bool StringArray::operator== (const StringArray& other) const throw() { 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 throw() { return ! operator== (other); } void StringArray::clear() { strings.clear(); } const String& StringArray::operator[] (const int index) const throw() { if (isPositiveAndBelow (index, strings.size())) return strings.getReference (index); return String::empty; } String& StringArray::getReference (const int index) throw() { 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) throw() { strings.move (currentIndex, newIndex); } const 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 int separatorLen = separator.length(); int charsNeeded = separatorLen * (last - start - 1); for (int i = start; i < last; ++i) charsNeeded += strings.getReference(i).length(); String result; result.preallocateStorage (charsNeeded); juce_wchar* dest = result; while (start < last) { const String& s = strings.getReference (start); const int len = s.length(); if (len > 0) { s.copyToUnicode (dest, len); dest += len; } if (++start < last && separatorLen > 0) { separator.copyToUnicode (dest, separatorLen); dest += separatorLen; } } *dest = 0; 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; if (text.isNotEmpty()) { bool insideQuotes = false; juce_wchar currentQuoteChar = 0; int i = 0; int tokenStart = 0; for (;;) { const juce_wchar c = text[i]; const bool isBreak = (c == 0) || ((! insideQuotes) && breakCharacters.containsChar (c)); if (! isBreak) { if (quoteCharacters.containsChar (c)) { if (insideQuotes) { // only break out of quotes-mode if we find a matching quote to the // one that we opened with.. if (currentQuoteChar == c) insideQuotes = false; } else { insideQuotes = true; currentQuoteChar = c; } } } else { add (String (static_cast (text) + tokenStart, i - tokenStart)); ++num; tokenStart = i + 1; } if (c == 0) break; ++i; } } return num; } int StringArray::addLines (const String& sourceText) { int numLines = 0; const juce_wchar* text = sourceText; while (*text != 0) { const juce_wchar* const startOfLine = text; while (*text != 0) { if (*text == '\r') { ++text; if (*text == '\n') ++text; break; } if (*text == '\n') { ++text; break; } ++text; } const juce_wchar* endOfLine = text; if (endOfLine > startOfLine && (*(endOfLine - 1) == '\r' || *(endOfLine - 1) == '\n')) --endOfLine; if (endOfLine > startOfLine && (*(endOfLine - 1) == '\r' || *(endOfLine - 1) == '\n')) --endOfLine; add (String (startOfLine, jmax (0, (int) (endOfLine - startOfLine)))); ++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, const juce_wchar* preNumberString, const juce_wchar* postNumberString) { if (preNumberString == 0) preNumberString = L" ("; if (postNumberString == 0) postNumberString = L")"; 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 + preNumberString + String (++number) + postNumberString; else ++number; while (nextIndex >= 0) { set (nextIndex, (*this)[nextIndex] + preNumberString + String (++number) + 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)]; } const 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; } const 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() throw() {} StringPool::~StringPool() {} namespace StringPoolHelpers { template const juce_wchar* 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); } else { const String& startString = strings.getReference (start); if (startString == newString) return startString; const int halfway = (start + end) >> 1; if (halfway == start) { if (startString.compare (newString) < 0) ++start; strings.insert (start, newString); return strings.getReference (start); } const int comp = strings.getReference (halfway).compare (newString); if (comp == 0) return strings.getReference (halfway); else if (comp < 0) start = halfway; else end = halfway; } } } } const juce_wchar* StringPool::getPooledString (const String& s) { if (s.isEmpty()) return String::empty; return StringPoolHelpers::getPooledStringFromArray (strings, s); } const juce_wchar* StringPool::getPooledString (const char* const s) { if (s == 0 || *s == 0) return String::empty; return StringPoolHelpers::getPooledStringFromArray (strings, s); } const juce_wchar* StringPool::getPooledString (const juce_wchar* const s) { if (s == 0 || *s == 0) return String::empty; return StringPoolHelpers::getPooledStringFromArray (strings, s); } int StringPool::size() const throw() { return strings.size(); } const juce_wchar* StringPool::operator[] (const int index) const throw() { return strings [index]; } 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), ignoreEmptyTextElements (true) { } XmlDocument::XmlDocument (const File& file) : 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) throw() { inputSource = newSource; } void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) throw() { ignoreEmptyTextElements = shouldBeIgnored; } namespace XmlIdentifierChars { bool isIdentifierCharSlow (const juce_wchar c) throw() { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '-' || c == ':' || c == '.'; } bool isIdentifierChar (const juce_wchar c) throw() { static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; return (c < numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) : isIdentifierCharSlow (c); } /*static void generateIdentifierCharConstants() { uint32 n[8]; zerostruct (n); 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 != 0) { ScopedPointer in (inputSource->createInputStream()); if (in != 0) { MemoryOutputStream data; data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); textToParse = data.toString(); if (! onlyReadOuterDocumentElement) originalText = textToParse; } } input = textToParse; lastError = String::empty; errorOccurred = false; outOfData = false; needToLoadDTD = true; if (textToParse.isEmpty()) { lastError = "not enough input"; } else { skipHeader(); if (input != 0) { ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); if (! errorOccurred) return result.release(); } else { lastError = "incorrect xml header"; } } return 0; } const String& XmlDocument::getLastParseError() const throw() { return lastError; } void XmlDocument::setLastError (const String& desc, const bool carryOn) { lastError = desc; errorOccurred = ! carryOn; } const String XmlDocument::getFileContents (const String& filename) const { if (inputSource != 0) { const ScopedPointer in (inputSource->createInputStreamFor (filename.trim().unquoted())); if (in != 0) return in->readEntireStreamAsString(); } return String::empty; } juce_wchar XmlDocument::readNextChar() throw() { if (*input != 0) return *input++; outOfData = true; return 0; } int XmlDocument::findNextTokenLength() throw() { int len = 0; juce_wchar c = *input; while (XmlIdentifierChars::isIdentifierChar (c)) c = input [++len]; return len; } void XmlDocument::skipHeader() { const juce_wchar* const found = CharacterFunctions::find (input, JUCE_T("")); if (input == 0) return; #if JUCE_DEBUG const String header (found, input - found); 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 += 2; } skipNextWhiteSpace(); const juce_wchar* docType = CharacterFunctions::find (input, JUCE_T(" 0) { const juce_wchar c = readNextChar(); if (outOfData) return; if (c == '<') ++n; else if (c == '>') --n; } docType += 9; dtdText = String (docType, (int) (input - (docType + 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 juce_wchar* const closeComment = CharacterFunctions::find (input, JUCE_T("-->")); if (closeComment == 0) { outOfData = true; break; } input = closeComment + 3; continue; } else if (input[1] == '?') { const juce_wchar* const closeBracket = CharacterFunctions::find (input, JUCE_T("?>")); 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 juce_wchar* const start = input; for (;;) { const juce_wchar character = *input; if (character == quote) { result.append (start, (int) (input - start)); ++input; return; } else if (character == '&') { result.append (start, (int) (input - start)); break; } else if (character == 0) { outOfData = true; setLastError ("unmatched quotes", false); break; } ++input; } } } } XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) { XmlElement* node = 0; skipNextWhiteSpace(); if (outOfData) return 0; input = CharacterFunctions::find (input, JUCE_T("<")); if (input != 0) { ++input; 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 juce_wchar* 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 juce_wchar* const preWhitespaceInput = input; skipNextWhiteSpace(); if (outOfData) { setLastError ("unmatched tags", false); break; } if (*input == '<') { if (input[1] == '/') { // our close tag.. input = CharacterFunctions::find (input, JUCE_T(">")); ++input; 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 juce_wchar* const 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 != 0) childAppender.append (n); else return; } } 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 juce_wchar* const oldInput = input; const bool oldOutOfData = outOfData; input = entity; outOfData = false; for (;;) { XmlElement* const n = readNextElement (true); if (n == 0) break; childAppender.append (n); } input = oldInput; outOfData = oldOutOfData; } else { textElementContent += entity; } } else { const juce_wchar* 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.append (start, len); } } if ((! ignoreEmptyTextElements) || textElementContent.containsNonWhitespaceChars()) { childAppender.append (XmlElement::createTextElement (textElementContent)); } } } } void XmlDocument::readEntity (String& result) { // skip over the ampersand ++input; if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("amp;"), 4) == 0) { input += 4; result += '&'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("quot;"), 5) == 0) { input += 5; result += '"'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("apos;"), 5) == 0) { input += 5; result += '\''; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("lt;"), 3) == 0) { input += 3; result += '<'; } else if (CharacterFunctions::compareIgnoreCase (input, JUCE_T("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 juce_wchar* const entityNameStart = input; const juce_wchar* const closingSemiColon = CharacterFunctions::find (input, JUCE_T(";")); if (closingSemiColon == 0) { outOfData = true; result += '&'; } else { input = closingSemiColon + 1; result += expandExternalEntity (String (entityNameStart, (int) (closingSemiColon - entityNameStart))); } } } const 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] == '#') { if (ent[1] == 'x' || ent[1] == 'X') return String::charToString (static_cast (ent.substring (2).getHexValue32())); if (ent[1] >= '0' && ent[1] <= '9') return String::charToString (static_cast (ent.substring (1).getIntValue())); setLastError ("illegal escape sequence", false); return String::charToString ('&'); } return expandExternalEntity (ent); } const 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; } const 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) throw() : name (other.name), value (other.value) { } XmlElement::XmlAttributeNode::XmlAttributeNode (const String& name_, const String& value_) throw() : name (name_), value (value_) { #if JUCE_DEBUG // this checks whether the attribute name string contains any illegals characters.. for (const juce_wchar* t = name; *t != 0; ++t) jassert (CharacterFunctions::isLetterOrDigit (*t) || *t == '_' || *t == '-' || *t == ':'); #endif } inline bool XmlElement::XmlAttributeNode::hasName (const String& nameToMatch) const throw() { return name.equalsIgnoreCase (nameToMatch); } XmlElement::XmlElement (const String& tagName_) throw() : 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*/) throw() { } 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() == 0); firstChildElement.addCopyOfList (other.firstChildElement); jassert (attributes.get() == 0); attributes.addCopyOfList (other.attributes); } XmlElement::~XmlElement() throw() { firstChildElement.deleteAll(); attributes.deleteAll(); } namespace XmlOutputFunctions { /*bool isLegalXmlCharSlow (const juce_wchar character) throw() { 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]; zerostruct (n); 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) throw() { 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) { const juce_wchar* t = text; for (;;) { const juce_wchar character = *t++; if (character == 0) break; if (isLegalXmlChar ((uint32) 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) (unsigned int) character) << ';'; break; } } } } void writeSpaces (OutputStream& out, int numSpaces) { if (numSpaces > 0) { const char blanks[] = " "; const int blankSize = (int) numElementsInArray (blanks) - 1; while (numSpaces > blankSize) { out.write (blanks, blankSize); numSpaces -= blankSize; } out.write (blanks, 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 != 0; 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 != 0) { outputStream.writeByte ('>'); XmlElement* child = firstChildElement; bool lastWasTextNode = false; while (child != 0) { 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); } } const 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 != 0) { writeToStream (*out, dtdToUse, false, true, encodingType, lineWrapLength); out = 0; return tempFile.overwriteTargetFileWithTemporary(); } } return false; } bool XmlElement::hasTagName (const String& tagNameWanted) const throw() { #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 != 0 && ! e->hasTagName (requiredTagName)) e = e->nextListItem; return e; } int XmlElement::getNumAttributes() const throw() { return attributes.size(); } const String& XmlElement::getAttributeName (const int index) const throw() { const XmlAttributeNode* const att = attributes [index]; return att != 0 ? att->name : String::empty; } const String& XmlElement::getAttributeValue (const int index) const throw() { const XmlAttributeNode* const att = attributes [index]; return att != 0 ? att->value : String::empty; } bool XmlElement::hasAttribute (const String& attributeName) const throw() { for (const XmlAttributeNode* att = attributes; att != 0; att = att->nextListItem) if (att->hasName (attributeName)) return true; return false; } const String& XmlElement::getStringAttribute (const String& attributeName) const throw() { for (const XmlAttributeNode* att = attributes; att != 0; att = att->nextListItem) if (att->hasName (attributeName)) return att->value; return String::empty; } const String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const { for (const XmlAttributeNode* att = attributes; att != 0; 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 != 0; 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 != 0; 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 != 0; 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 throw() { for (const XmlAttributeNode* att = attributes; att != 0; 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 == 0) { attributes = new XmlAttributeNode (attributeName, value); } else { XmlAttributeNode* att = attributes; for (;;) { if (att->hasName (attributeName)) { att->value = value; break; } else if (att->nextListItem == 0) { 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) throw() { LinkedListPointer* att = &attributes; while (att->get() != 0) { if (att->get()->hasName (attributeName)) { delete att->removeNext(); break; } att = &(att->get()->nextListItem); } } void XmlElement::removeAllAttributes() throw() { attributes.deleteAll(); } int XmlElement::getNumChildElements() const throw() { return firstChildElement.size(); } XmlElement* XmlElement::getChildElement (const int index) const throw() { return firstChildElement [index].get(); } XmlElement* XmlElement::getChildByName (const String& childName) const throw() { XmlElement* child = firstChildElement; while (child != 0) { if (child->hasTagName (childName)) break; child = child->nextListItem; } return child; } void XmlElement::addChildElement (XmlElement* const newNode) throw() { if (newNode != 0) firstChildElement.append (newNode); } void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) throw() { if (newNode != 0) { 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) throw() { if (newNode != 0) { LinkedListPointer* const p = firstChildElement.findPointerTo (currentChildElement); if (p != 0) { if (currentChildElement != newNode) delete p->replaceNext (newNode); return true; } } return false; } void XmlElement::removeChildElement (XmlElement* const childToRemove, const bool shouldDeleteTheChild) throw() { if (childToRemove != 0) { firstChildElement.remove (childToRemove); if (shouldDeleteTheChild) delete childToRemove; } } bool XmlElement::isEquivalentTo (const XmlElement* const other, const bool ignoreOrderOfAttributes) const throw() { if (this != other) { if (other == 0 || tagName != other->tagName) return false; if (ignoreOrderOfAttributes) { int totalAtts = 0; const XmlAttributeNode* att = attributes; while (att != 0) { 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 == 0 || otherAtt == 0) { 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 == 0 || otherChild == 0) { 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() throw() { firstChildElement.deleteAll(); } void XmlElement::deleteAllChildElementsWithTagName (const String& name) throw() { XmlElement* child = firstChildElement; while (child != 0) { XmlElement* const nextChild = child->nextListItem; if (child->hasTagName (name)) removeChildElement (child, true); child = nextChild; } } bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const throw() { return firstChildElement.contains (possibleChild); } XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) throw() { if (this == elementToLookFor || elementToLookFor == 0) return 0; XmlElement* child = firstChildElement; while (child != 0) { if (elementToLookFor == child) return this; XmlElement* const found = child->findParentElementOf (elementToLookFor); if (found != 0) return found; child = child->nextListItem; } return 0; } void XmlElement::getChildElementsAsArray (XmlElement** elems) const throw() { firstChildElement.copyToArray (elems); } void XmlElement::reorderChildElements (XmlElement** const elems, const int num) throw() { XmlElement* e = firstChildElement = elems[0]; for (int i = 1; i < num; ++i) { e->nextListItem = elems[i]; e = e->nextListItem; } e->nextListItem = 0; } bool XmlElement::isTextElement() const throw() { return tagName.isEmpty(); } static const juce_wchar* const juce_xmltextContentAttributeName = L"text"; const String& XmlElement::getText() const throw() { 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. } const String XmlElement::getAllSubText() const { if (isTextElement()) return getText(); String result; String::Concatenator concatenator (result); const XmlElement* child = firstChildElement; while (child != 0) { concatenator.append (child->getAllSubText()); child = child->nextListItem; } return result; } const String XmlElement::getChildElementAllSubText (const String& childTagName, const String& defaultReturnValue) const { const XmlElement* const child = getChildByName (childTagName); if (child != 0) 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() throw() { XmlElement* child = firstChildElement; while (child != 0) { 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_ReadWriteLock.cpp ***/ BEGIN_JUCE_NAMESPACE ReadWriteLock::ReadWriteLock() throw() : numWaitingWriters (0), numWriters (0), writerThreadId (0) { } ReadWriteLock::~ReadWriteLock() throw() { jassert (readerThreads.size() == 0); jassert (numWriters == 0); } void ReadWriteLock::enterRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock 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 ScopedUnlock ul (accessLock); waitEvent.wait (100); } } void ReadWriteLock::exitRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock 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 throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock 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 throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); const ScopedLock 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 throw() { const ScopedLock 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() { } void add (Thread* const thread) { const ScopedLock sl (lock); jassert (! threads.contains (thread)); threads.add (thread); } void remove (Thread* const thread) { const ScopedLock sl (lock); jassert (threads.contains (thread)); threads.removeValue (thread); } int size() const throw() { return threads.size(); } Thread* getThreadWithID (const Thread::ThreadID targetID) const throw() { const ScopedLock sl (lock); for (int i = threads.size(); --i >= 0;) { Thread* const t = threads.getUnchecked(i); if (t->getThreadId() == targetID) return t; } return 0; } void stopAll (const int timeOutMilliseconds) { signalAllThreadsToStop(); for (;;) { Thread* firstThread = getFirstThread(); if (firstThread != 0) firstThread->stopThread (timeOutMilliseconds); else break; } } static RunningThreadsList& getInstance() { static RunningThreadsList runningThreads; return runningThreads; } private: Array threads; CriticalSection lock; void signalAllThreadsToStop() { const ScopedLock sl (lock); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); } Thread* getFirstThread() const { const ScopedLock 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_ (0), 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_ == 0) { launchThread(); setThreadPriority (threadHandle_, threadPriority_); startSuspensionEvent_.signal(); } } void Thread::startThread (const int priority) { const ScopedLock sl (startStopLock); if (threadHandle_ == 0) { threadPriority_ = priority; startThread(); } else { setPriority (priority); } } bool Thread::isThreadRunning() const { return threadHandle_ != 0; } 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_ = 0; 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); } 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 (0), 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 == 0 || ! pool->contains (this)); } const 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 != 0); jassert (job->pool == 0); if (job->pool == 0) { 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 != 0) { 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 != 0) { const ScopedLock sl (lock); if (jobs.contains (job)) { if (job->isActive) { if (interruptIfRunning) job->signalJobShouldExit(); dontWait = false; } else { jobs.removeValue (job); job->pool = 0; } } } 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 == 0 || selectedJobsToRemove->isJobSuitable (job)) { if (job->isActive) { jobsToWaitFor.add (job); if (interruptRunningJobs) job->signalJobShouldExit(); } else { jobs.remove (i); if (deleteInactiveJobs) delete job; else job->pool = 0; } } } } 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; } const 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 = 0; { const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { job = jobs[i]; if (job != 0 && ! (job->isActive || job->shouldStop)) break; job = 0; } if (job != 0) job->isActive = true; } if (job != 0) { 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 = 0; 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), index (0), clientBeingCalled (0), clientsChanged (false) { } TimeSliceThread::~TimeSliceThread() { stopThread (2000); } void TimeSliceThread::addTimeSliceClient (TimeSliceClient* const client) { const ScopedLock sl (listLock); clients.addIfNotAlreadyThere (client); clientsChanged = true; notify(); } void TimeSliceThread::removeTimeSliceClient (TimeSliceClient* const client) { const ScopedLock sl1 (listLock); clientsChanged = true; // 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]; } void TimeSliceThread::run() { int numCallsSinceBusy = 0; while (! threadShouldExit()) { int timeToWait = 500; { const ScopedLock sl (callbackLock); { const ScopedLock sl2 (listLock); if (clients.size() > 0) { index = (index + 1) % clients.size(); clientBeingCalled = clients [index]; } else { index = 0; clientBeingCalled = 0; } if (clientsChanged) { clientsChanged = false; numCallsSinceBusy = 0; } } if (clientBeingCalled != 0) { if (clientBeingCalled->useTimeSlice()) numCallsSinceBusy = 0; else ++numCallsSinceBusy; if (numCallsSinceBusy >= clients.size()) timeToWait = 500; else if (index == 0) timeToWait = 1; // throw in an occasional pause, to stop everything locking up else timeToWait = 0; } } if (timeToWait > 0) wait (timeToWait); } } END_JUCE_NAMESPACE /*** End of inlined file: juce_TimeSliceThread.cpp ***/ /*** Start of inlined file: juce_DeletedAtShutdown.cpp ***/ BEGIN_JUCE_NAMESPACE DeletedAtShutdown::DeletedAtShutdown() { const ScopedLock sl (getLock()); getObjects().add (this); } DeletedAtShutdown::~DeletedAtShutdown() { const ScopedLock sl (getLock()); 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 ScopedLock sl (getLock()); 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 ScopedLock sl (getLock()); if (! getObjects().contains (deletee)) deletee = 0; } 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 } CriticalSection& DeletedAtShutdown::getLock() { static CriticalSection lock; return lock; } Array & DeletedAtShutdown::getObjects() { static Array objects; return objects; } END_JUCE_NAMESPACE /*** End of inlined file: juce_DeletedAtShutdown.cpp ***/ /*** Start of inlined file: juce_UnitTest.cpp ***/ BEGIN_JUCE_NAMESPACE UnitTest::UnitTest (const String& name_) : name (name_), runner (0) { 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_ != 0); 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 (0), assertOnFailure (false) { } UnitTestRunner::~UnitTestRunner() { } int UnitTestRunner::getNumResults() const throw() { return results.size(); } const UnitTestRunner::TestResult* UnitTestRunner::getResult (int index) const throw() { 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 != 0); // 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 != 0); // 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 ***/ #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, 0); else target->setProperty (name, newValue, 0); return true; } bool undo() { if (isAddingNewProperty) target->removeProperty (name, 0); else target->setProperty (name, oldValue, 0); 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 != 0 && next->target == target && next->name == name && ! (next->isAddingNewProperty || next->isDeletingProperty)) { return new SetPropertyAction (target, name, next->newValue, oldValue, false, false); } } return 0; } 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_ != 0 ? newChild_ : target_->children [childIndex_]), childIndex (childIndex_), isDeleting (newChild_ == 0) { jassert (child != 0); } bool perform() { if (isDeleting) target->removeChild (childIndex, 0); else target->addChild (child, childIndex, 0); return true; } bool undo() { if (isDeleting) { target->addChild (child, childIndex, 0); } 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, 0); } 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, 0); return true; } bool undo() { parent->moveChild (endIndex, startIndex, 0); return true; } int getSizeInUnits() { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) { MoveChildAction* next = dynamic_cast (nextAction); if (next != 0 && next->parent == parent && next->startIndex == endIndex) return new MoveChildAction (parent, startIndex, next->endIndex); return 0; } private: const SharedObjectPtr parent; const int startIndex, endIndex; JUCE_DECLARE_NON_COPYABLE (MoveChildAction); }; ValueTree::SharedObject::SharedObject (const Identifier& type_) : type (type_), parent (0) { } ValueTree::SharedObject::SharedObject (const SharedObject& other) : type (other.type), properties (other.properties), parent (0) { 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 == 0); // 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 = 0; 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 != 0) v->listeners.call (&ValueTree::Listener::valueTreePropertyChanged, tree, property); } } void ValueTree::SharedObject::sendPropertyChangeMessage (const Identifier& property) { ValueTree tree (this); ValueTree::SharedObject* t = this; while (t != 0) { t->sendPropertyChangeMessage (tree, property); t = t->parent; } } void ValueTree::SharedObject::sendChildChangeMessage (ValueTree& tree) { for (int i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != 0) v->listeners.call (&ValueTree::Listener::valueTreeChildrenChanged, tree); } } void ValueTree::SharedObject::sendChildChangeMessage() { ValueTree tree (this); ValueTree::SharedObject* t = this; while (t != 0) { t->sendChildChangeMessage (tree); t = t->parent; } } void ValueTree::SharedObject::sendParentChangeMessage() { ValueTree tree (this); int i; for (i = children.size(); --i >= 0;) { SharedObject* const t = children[i]; if (t != 0) t->sendParentChangeMessage(); } for (i = valueTreesWithListeners.size(); --i >= 0;) { ValueTree* const v = valueTreesWithListeners[i]; if (v != 0) 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 == 0) { if (properties.set (name, newValue)) sendPropertyChangeMessage (name); } else { var* const existingValue = properties.getVarPointer (name); if (existingValue != 0) { if (*existingValue != newValue) undoManager->perform (new SetPropertyAction (this, name, newValue, properties [name], 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 == 0) { 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 == 0) { 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 != 0) { 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 != 0 && 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 == 0); if (child->parent != 0) { jassert (child->parent->children.indexOf (child) >= 0); child->parent->removeChild (child->parent->children.indexOf (child), undoManager); } if (undoManager == 0) { children.insert (index, child); child->parent = this; sendChildChangeMessage(); 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 != 0) { if (undoManager == 0) { children.remove (childIndex); child->parent = 0; sendChildChangeMessage(); child->sendParentChangeMessage(); } else { undoManager->perform (new AddOrRemoveChildAction (this, childIndex, 0)); } } } 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 == 0) { children.move (currentIndex, newIndex); sendChildChangeMessage(); } 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 == 0) { children = newOrder; sendChildChangeMessage(); } else { for (int i = 0; i < children.size(); ++i) { if (children.getUnchecked(i) != newOrder.getUnchecked(i)) { jassert (children.contains (newOrder.getUnchecked(i))); moveChild (children.indexOf (newOrder.getUnchecked(i)), 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() throw() : object (0) { } 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 != 0) object->valueTreesWithListeners.removeValue (this); if (other.object != 0) other.object->valueTreesWithListeners.add (this); } object = other.object; return *this; } ValueTree::~ValueTree() { if (listeners.size() > 0 && object != 0) object->valueTreesWithListeners.removeValue (this); } bool ValueTree::operator== (const ValueTree& other) const throw() { return object == other.object; } bool ValueTree::operator!= (const ValueTree& other) const throw() { return object != other.object; } bool ValueTree::isEquivalentTo (const ValueTree& other) const { return object == other.object || (object != 0 && other.object != 0 && object->isEquivalentTo (*other.object)); } ValueTree ValueTree::createCopy() const { return ValueTree (object != 0 ? new SharedObject (*object) : 0); } bool ValueTree::hasType (const Identifier& typeName) const { return object != 0 && object->type == typeName; } const Identifier ValueTree::getType() const { return object != 0 ? object->type : Identifier(); } ValueTree ValueTree::getParent() const { return ValueTree (object != 0 ? object->parent : (SharedObject*) 0); } ValueTree ValueTree::getSibling (const int delta) const { if (object == 0 || object->parent == 0) 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 == 0 ? var::null : object->getProperty (name); } const var& ValueTree::getProperty (const Identifier& name) const { return object == 0 ? var::null : object->getProperty (name); } const var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const { return object == 0 ? defaultReturnValue : object->getProperty (name, defaultReturnValue); } void ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) { jassert (name.toString().isNotEmpty()); if (object != 0 && name.toString().isNotEmpty()) object->setProperty (name, newValue, undoManager); } bool ValueTree::hasProperty (const Identifier& name) const { return object != 0 && object->hasProperty (name); } void ValueTree::removeProperty (const Identifier& name, UndoManager* const undoManager) { if (object != 0) object->removeProperty (name, undoManager); } void ValueTree::removeAllProperties (UndoManager* const undoManager) { if (object != 0) object->removeAllProperties (undoManager); } int ValueTree::getNumProperties() const { return object == 0 ? 0 : object->properties.size(); } const Identifier ValueTree::getPropertyName (const int index) const { return object == 0 ? 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 valueTreeChildrenChanged (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 == 0 ? 0 : object->children.size(); } ValueTree ValueTree::getChild (int index) const { return ValueTree (object != 0 ? (SharedObject*) object->children [index] : (SharedObject*) 0); } ValueTree ValueTree::getChildWithName (const Identifier& type) const { return object != 0 ? object->getChildWithName (type) : ValueTree::invalid; } ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) { return object != 0 ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; } ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { return object != 0 ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; } bool ValueTree::isAChildOf (const ValueTree& possibleParent) const { return object != 0 && object->isAChildOf (possibleParent.object); } int ValueTree::indexOf (const ValueTree& child) const { return object != 0 ? object->indexOf (child) : -1; } void ValueTree::addChild (const ValueTree& child, int index, UndoManager* const undoManager) { if (object != 0) object->addChild (child.object, index, undoManager); } void ValueTree::removeChild (const int childIndex, UndoManager* const undoManager) { if (object != 0) object->removeChild (childIndex, undoManager); } void ValueTree::removeChild (const ValueTree& child, UndoManager* const undoManager) { if (object != 0) object->removeChild (object->children.indexOf (child.object), undoManager); } void ValueTree::removeAllChildren (UndoManager* const undoManager) { if (object != 0) object->removeAllChildren (undoManager); } void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { if (object != 0) object->moveChild (currentIndex, newIndex, undoManager); } void ValueTree::addListener (Listener* listener) { if (listener != 0) { if (listeners.size() == 0 && object != 0) object->valueTreesWithListeners.add (this); listeners.add (listener); } } void ValueTree::removeListener (Listener* listener) { listeners.remove (listener); if (listeners.size() == 0 && object != 0) 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 != 0 ? object->createXml() : 0; } ValueTree ValueTree::fromXml (const XmlElement& xml) { ValueTree v (xml.getTagName()); v.object->properties.setFromXmlAttributes (xml); forEachXmlChildElement (xml, e) v.addChild (fromXml (*e), -1, 0); 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) { for (int i = valuesWithListeners.size(); --i >= 0;) { Value* const v = valuesWithListeners[i]; if (v != 0) v->callListeners(); } } else { triggerAsyncUpdate(); } } void Value::ValueSource::handleAsyncUpdate() { sendChangeMessage (true); } class SimpleValueSource : public Value::ValueSource { public: SimpleValueSource() { } SimpleValueSource (const var& initialValue) : value (initialValue) { } ~SimpleValueSource() { } const var getValue() const { return value; } void setValue (const var& newValue) { if (newValue != 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_ != 0); } 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); } const var Value::getValue() const { return value->getValue(); } Value::operator const var() const { return getValue(); } void Value::setValue (const var& newValue) { value->setValue (newValue); } const 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 != 0) { 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 JUCEApplication::JUCEApplication() : appReturnValue (0), stillInitialising (true) { jassert (isStandaloneApp() && appInstance == 0); appInstance = this; } JUCEApplication::~JUCEApplication() { if (appLock != 0) { appLock->exit(); appLock = 0; } jassert (appInstance == this); appInstance = 0; } JUCEApplication::CreateInstanceFunction JUCEApplication::createInstance = 0; JUCEApplication* JUCEApplication::appInstance = 0; 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) throw() { appReturnValue = newReturnValue; } void JUCEApplication::actionListenerCallback (const String& message) { if (message.startsWith (getApplicationName() + "/")) anotherInstanceStarted (message.substring (getApplicationName().length() + 1)); } 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 != 0) appInstance->unhandledException (e, sourceFile, lineNumber); } ApplicationCommandTarget* JUCEApplication::getNextCommandTarget() { return 0; } 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 == 0); // initialiseApp must only be called once! if (! moreThanOneInstanceAllowed()) { appLock = new InterProcessLock ("juceAppLock_" + getApplicationName()); if (! appLock->enter(0)) { appLock = 0; 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 // register for broadcast new app messages MessageManager::getInstance()->registerBroadcastListener (this); stillInitialising = false; return true; } int JUCEApplication::shutdownApp() { jassert (appInstance == this); MessageManager::getInstance()->deregisterBroadcastListener (this); 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 != 0) app->shutdownApp(); } shutdownJuce_GUI(); } int JUCEApplication::main (const String& commandLine) { ScopedJuceInitialiser_GUI libraryInitialiser; jassert (createInstance != 0); 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 != 0); 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 } 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_) throw() : commandID (commandID_), flags (0) { } void ApplicationCommandInfo::setInfo (const String& shortName_, const String& description_, const String& categoryName_, const int flags_) throw() { shortName = shortName_; description = description_; categoryName = categoryName_; flags = flags_; } void ApplicationCommandInfo::setActive (const bool b) throw() { if (b) flags &= ~isDisabled; else flags |= isDisabled; } void ApplicationCommandInfo::setTicked (const bool b) throw() { if (b) flags |= isTicked; else flags &= ~isTicked; } void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, const ModifierKeys& modifiers) throw() { 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 (0) { keyMappings = new KeyPressMappingSet (this); Desktop::getInstance().addFocusChangeListener (this); } ApplicationCommandManager::~ApplicationCommandManager() { Desktop::getInstance().removeFocusChangeListener (this); keyMappings = 0; } 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 != 0) { 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 throw() { for (int i = commands.size(); --i >= 0;) if (commands.getUnchecked(i)->commandID == commandID) return commands.getUnchecked(i); return 0; } const String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const throw() { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return (ci != 0) ? ci->shortName : String::empty; } const String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const throw() { const ApplicationCommandInfo* const ci = getCommandForID (commandID); return (ci != 0) ? (ci->description.isNotEmpty() ? ci->description : ci->shortName) : String::empty; } const 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 == 0) 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 != 0 ? firstTarget : findDefaultComponentTarget(); } void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) throw() { firstTarget = newTarget; } ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID, ApplicationCommandInfo& upToDateInfo) { ApplicationCommandTarget* target = getFirstCommandTarget (commandID); if (target == 0) target = JUCEApplication::getInstance(); if (target != 0) target = target->getTargetForCommand (commandID); if (target != 0) target->getCommandInfo (commandID, upToDateInfo); return target; } ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c) { ApplicationCommandTarget* target = dynamic_cast (c); if (target == 0 && c != 0) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) target = c->findParentComponentOfClass ((ApplicationCommandTarget*) 0); return target; } ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget() { Component* c = Component::getCurrentlyFocusedComponent(); if (c == 0) { TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow(); if (activeWindow != 0) { c = activeWindow->getPeer()->getLastFocusedSubcomponent(); if (c == 0) c = activeWindow; } } if (c == 0 && 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 != 0) return target; } } if (c != 0) { 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 != 0 && resizableWindow->getContentComponent() != 0) c = resizableWindow->getContentComponent(); ApplicationCommandTarget* const target = findTargetForComponent (c); if (target != 0) 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 = 0; } bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async) { if (isCommandActive (info.commandID)) { if (async) { if (messageInvoker == 0) 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 != 0) // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) return c->findParentComponentOfClass ((ApplicationCommandTarget*) 0); return 0; } ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID) { ApplicationCommandTarget* target = this; int depth = 0; while (target != 0) { 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 == 0) { target = JUCEApplication::getInstance(); if (target != 0) { Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) return target; } } return 0; } 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 != 0) { 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 == 0) { target = JUCEApplication::getInstance(); if (target != 0) 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 (0), 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 (0) { } 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 != 0 && ! userOk) filenames << '\n' << userProps->getFile().getFullPathName(); if (commonProps != 0 && ! commonOk) filenames << '\n' << commonProps->getFile().getFullPathName(); AlertWindow::showMessageBox (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 == 0) userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, false, msBeforeSaving, options, processLock); if (commonProps == 0) commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, true, msBeforeSaving, options, processLock); userProps->setFallbackPropertySet (commonProps); } } PropertiesFile* ApplicationProperties::getUserSettings() { if (userProps == 0) openFiles(); return userProps; } PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) { if (commonProps == 0) openFiles(); if (returnUserPropsIfReadOnly) { if (commonSettingsAreReadOnly == 0) commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; if (commonSettingsAreReadOnly > 0) return userProps; } return commonProps; } bool ApplicationProperties::saveIfNeeded() { return (userProps == 0 || userProps->saveIfNeeded()) && (commonProps == 0 || commonProps->saveIfNeeded()); } void ApplicationProperties::closeFiles() { userProps = 0; commonProps = 0; } 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 != 0 && ! pl->isLocked()) return; // locking failure.. ScopedPointer fileStream (f.createInputStream()); if (fileStream != 0) { 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 = 0; XmlDocument parser (f); ScopedPointer doc (parser.getDocumentElement (true)); if (doc != 0 && doc->hasTagName (PropertyFileConstants::fileTag)) { doc = parser.getDocumentElement(); if (doc != 0) { loadedOk = true; forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) { const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); if (name.isNotEmpty()) { getAllProperties().set (name, e->getFirstChildElement() != 0 ? 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 != 0 ? new InterProcessLock::ScopedLockType (*processLock) : 0; } 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 != 0) e->addChildElement (childElement); else e->setAttribute (PropertyFileConstants::valueAttribute, getAllProperties().getAllValues() [i]); } ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. if (doc.writeToFile (file, String::empty)) { needsWriting = false; return true; } } else { ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != 0) { 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 = 0; 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(); } const 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); #endif #ifdef JUCE_LINUX const File dir ((commonToAllUsers ? "/var/" : "~/") + (folderName.isNotEmpty() ? folderName : ("." + applicationName))); #endif #if 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 0; 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(); } } 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; } 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); while (getNumFiles() > maxNumberOfItems) files.remove (getNumFiles() - 1); } int RecentlyOpenedFilesList::getNumFiles() const { return files.size(); } const File RecentlyOpenedFilesList::getFile (const int index) const { return File (files [index]); } void RecentlyOpenedFilesList::clear() { files.clear(); } void RecentlyOpenedFilesList::addFile (const File& file) { const String path (file.getFullPathName()); files.removeString (path, true); files.insert (0, path); setMaxNumberOfItems (maxNumberOfItems); } 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 != 0) { const File** avoid = filesToAvoid; while (*avoid != 0) { if (f == **avoid) { needsAvoiding = true; break; } ++avoid; } } if (! needsAvoiding) { menuToAddTo.addItem (baseItemId + i, showFullPaths ? f.getFullPathName() : f.getFileName()); ++num; } } } return num; } const 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_ != 0) { 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 != 0 && ! newTransaction) { UndoableAction* lastAction = commandSet->getLast(); if (lastAction != 0) { UndoableAction* coalescedAction = lastAction->createCoalescedAction (command); if (coalescedAction != 0) { 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(); } const String UndoManager::getUndoDescription() const { return transactionNames [nextIndex - 1]; } const String UndoManager::getRedoDescription() const { return transactionNames [nextIndex]; } bool UndoManager::undo() { const OwnedArray* const commandSet = transactions [nextIndex - 1]; if (commandSet == 0) return false; reentrancyCheck = true; bool failed = false; for (int i = commandSet->size(); --i >= 0;) { if (! commandSet->getUnchecked(i)->undo()) { jassertfalse; failed = true; break; } } reentrancyCheck = false; if (failed) clearUndoHistory(); else --nextIndex; beginNewTransaction(); sendChangeMessage(); return true; } bool UndoManager::redo() { const OwnedArray* const commandSet = transactions [nextIndex]; if (commandSet == 0) return false; reentrancyCheck = true; bool failed = false; for (int i = 0; i < commandSet->size(); ++i) { if (! commandSet->getUnchecked(i)->perform()) { jassertfalse; failed = true; break; } } reentrancyCheck = false; 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 != 0 && ! 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 != 0 && ! newTransaction) return commandSet->size(); return 0; } END_JUCE_NAMESPACE /*** End of inlined file: juce_UndoManager.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 }; class AiffAudioFormatReader : public AudioFormatReader { public: int bytesPerFrame; int64 dataChunkStart; bool littleEndian; AiffAudioFormatReader (InputStream* in) : AudioFormatReader (in, TRANS (aiffFormatName)) { 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 ((hasGotVer && hasGotData && hasGotType) || chunkEnd < input->getPosition() || input->isExhausted()) { break; } input->setPosition (chunkEnd); } } } } ~AiffAudioFormatReader() { } 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] != 0) 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: static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader); }; class AiffAudioFormatWriter : public AudioFormatWriter { public: AiffAudioFormatWriter (OutputStream* out, double sampleRate_, unsigned int numChans, int bits) : AudioFormatWriter (out, TRANS (aiffFormatName), sampleRate_, numChans, bits), lengthInSamples (0), bytesWritten (0), writeFailed (false) { headerPosition = out->getPosition(); writeHeader(); } ~AiffAudioFormatWriter() { if ((bytesWritten & 1) != 0) output->writeByte (0); writeHeader(); } bool write (const int** data, int numSamples) { jassert (data != 0 && *data != 0); // 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; uint32 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } void writeHeader() { 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; 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]; zeromem (sampleRateBytes, 10); 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); 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 = 0; return 0; } 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); return 0; } 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] != 0) 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] != 0) { lastFullChannel = destSamples[i]; break; } } if (lastFullChannel != 0) for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != 0) memcpy (destSamples[i], lastFullChannel, sizeof (int) * numSamplesToRead); } else { for (int i = numChannels; i < numDestChannels; ++i) if (destSamples[i] != 0) 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]; zerostruct (buffers); 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 != 0) { int* b = *bufferChan++; if (isFloatingPoint()) { // int -> float const double factor = 1.0 / std::numeric_limits::max(); for (int i = 0; i < numToDo; ++i) ((float*) 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 (0), samplesWritten (0), isRunning (true) { timeSliceThread.addTimeSliceClient (this); } ~Buffer() { isRunning = false; timeSliceThread.removeTimeSliceClient (this); while (useTimeSlice()) {} } 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; } bool useTimeSlice() { const int numToDo = getTotalSize() / 4; int start1, size1, start2, size2; prepareToRead (numToDo, start1, size1, start2, size2); if (size1 <= 0) return false; writer->writeFromAudioSampleBuffer (buffer, start1, size1); const ScopedLock sl (thumbnailLock); if (thumbnailToUpdate != 0) thumbnailToUpdate->addBlock (samplesWritten, buffer, start1, size1); samplesWritten += size1; if (size2 > 0) { writer->writeFromAudioSampleBuffer (buffer, start2, size2); if (thumbnailToUpdate != 0) thumbnailToUpdate->addBlock (samplesWritten, buffer, start2, size2); samplesWritten += size2; } finishedRead (size1 + size2); return true; } void setThumbnail (AudioThumbnail* thumb) { if (thumb != 0) 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 != 0); if (newFormat != 0) { #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 0; } 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 != 0) { AudioFormatReader* const r = af->createReaderFor (in, true); if (r != 0) return r; } } } return 0; } 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 != 0) { const int64 originalStreamPos = in->getPosition(); for (int i = 0; i < getNumKnownFormats(); ++i) { AudioFormatReader* const r = getKnownFormat(i)->createReaderFor (in, false); if (r != 0) { 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 0; } 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] != 0) 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 { char minValue; char maxValue; MinMaxValue() : minValue (0), maxValue (0) { } inline void set (const char newMin, const char newMax) throw() { minValue = newMin; maxValue = newMax; } inline void setFloat (const float newMin, const float newMax) throw() { minValue = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f)); maxValue = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f)); if (maxValue == minValue) maxValue = (char) jmin (127, maxValue + 1); } inline bool isNonZero() const throw() { return maxValue > minValue; } inline int getPeak() const throw() { return jmax (std::abs ((int) minValue), std::abs ((int) maxValue)); } inline void read (InputStream& input) { minValue = input.readByte(); maxValue = input.readByte(); } inline void write (OutputStream& output) { output.writeByte (minValue); output.writeByte (maxValue); } }; class AudioThumbnail::LevelDataSource : public TimeSliceClient, public Timer { 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 = 2000 }; void initialise (int64 numSamplesFinished_) { const ScopedLock sl (readerLock); numSamplesFinished = numSamplesFinished_; createReader(); if (reader != 0) { lengthInSamples = reader->lengthInSamples; numChannels = reader->numChannels; sampleRate = reader->sampleRate; if (lengthInSamples <= 0) reader = 0; else if (! isFullyLoaded()) owner.cache.addTimeSliceClient (this); } } void getLevels (int64 startSample, int numSamples, Array& levels) { const ScopedLock sl (readerLock); createReader(); if (reader != 0) { 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 = 0; } bool useTimeSlice() { if (isFullyLoaded()) { if (reader != 0 && source != 0) startTimer (timeBeforeDeletingReader); owner.cache.removeTimeSliceClient (this); return false; } stopTimer(); bool justFinished = false; { const ScopedLock sl (readerLock); createReader(); if (reader != 0) { if (! readNextBlock()) return true; justFinished = true; } } if (justFinished) owner.cache.storeThumb (owner, hashCode); return false; } void timerCallback() { stopTimer(); releaseResources(); } bool isFullyLoaded() const throw() { return numSamplesFinished >= lengthInSamples; } inline int sampleToThumbSample (const int64 originalSample) const throw() { 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 == 0 && source != 0) { InputStream* audioFileStream = source->createInputStream(); if (audioFileStream != 0) reader = owner.formatManagerToUse.createReaderFor (audioFileStream); } } bool readNextBlock() { jassert (reader != 0); 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) throw() { jassert (thumbSampleIndex < data.size()); return data.getRawDataPointer() + thumbSampleIndex; } int getSize() const throw() { return data.size(); } void getMinMax (int startSample, int endSample, MinMaxValue& result) throw() { 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.minValue < mn) mn = v.minValue; if (v.maxValue > mx) mx = v.maxValue; ++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() { peakLevel = -1; } int getPeak() { 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->maxValue * vscale - 0.3f, topY), jmin (midY - cacheData->minValue * 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 != 0) { 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) throw() { 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 = 0; 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& input) { clear(); 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 area 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 != 0 && setDataSource (new LevelDataSource (*this, newSource)); } void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash) { clear(); if (newReader != 0) setDataSource (new LevelDataSource (*this, newReader, hash)); } int64 AudioThumbnail::getHashCode() const { return source == 0 ? 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); numSamplesFinished = jmax (numSamplesFinished, (thumbIndex + numValues) * (int64) samplesPerThumbSample); totalSamples = jmax (numSamplesFinished, totalSamples); window->invalidate(); sendChangeMessage(); } int AudioThumbnail::getNumChannels() const throw() { return numChannels; } double AudioThumbnail::getTotalLength() const throw() { return totalSamples / sampleRate; } bool AudioThumbnail::isFullyLoaded() const throw() { return numSamplesFinished >= totalSamples - samplesPerThumbSample; } int64 AudioThumbnail::getNumSamplesFinished() const throw() { 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::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 0; } bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) { ThumbnailCacheEntry* te = findThumbFor (hashCode); if (te != 0) { 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 == 0) { 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 != 0) DisposeHandle (dataHandle); if (extractor != 0) { MovieAudioExtractionEnd (extractor); extractor = 0; } 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] != 0) { const short* const src = ((const short*) bufferList->mBuffers[0].mData) + j; for (int i = 0; i < samplesReceived; ++i) destSamples[j][startOffsetInDestBuffer + i] = src [i << 1] << 16; } } startOffsetInDestBuffer += samplesReceived; startSampleInFile += samplesReceived; numSamples -= samplesReceived; if ((outFlags & kQTMovieAudioExtractionComplete) != 0 && numSamples > 0) { for (int j = numDestChannels; --j >= 0;) if (destSamples[j] != 0) 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 0; } AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/, unsigned int /*numberOfChannels*/, int /*bitsPerSample*/, const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/) { jassertfalse; // not yet implemented! return 0; } 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"; const 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; } #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; 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* s = (SMPLChunk*) 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.. 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 ExtensibleWavSubFormat { uint32 data1; uint16 data2; uint16 data3; uint8 data4[8]; } 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) { if (input->readInt() == chunkName ("RIFF")) { const uint32 len = (uint32) input->readInt(); const int64 end = input->getPosition() + len; bool hasGotType = false; bool hasGotData = false; if (input->readInt() == chunkName ("WAVE")) { 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")) { // get the data chunk's position dataLength = length; dataChunkStart = input->getPosition(); lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; hasGotData = true; } else if (chunkType == chunkName ("bext")) { bwavChunkStart = input->getPosition(); bwavSize = length; // Broadcast-wav extension chunk.. 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 (chunkEnd <= input->getPosition()) { break; } input->setPosition (chunkEnd); } } } } ~WavAudioFormatReader() { } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { jassert (destSamples != 0); const int64 samplesAvailable = lengthInSamples - startSampleInFile; if (samplesAvailable < numSamples) { for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != 0) 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; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } 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) { if (metadataValues.size() > 0) { bwavChunk = BWAVChunk::createFrom (metadataValues); smplChunk = SMPLChunk::createFrom (metadataValues); } headerPosition = out->getPosition(); writeHeader(); } ~WavAudioFormatWriter() { writeHeader(); } bool write (const int** data, int numSamples) { jassert (data != 0 && *data != 0); // 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: ScopedPointer converter; MemoryBlock tempBlock, bwavChunk, smplChunk; uint32 lengthInSamples, bytesWritten; int64 headerPosition; bool writeFailed; static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } void writeHeader() { 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; output->writeInt (chunkName ("RIFF")); output->writeInt ((int) (lengthInSamples * bytesPerFrame + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36))); output->writeInt (chunkName ("WAVE")); output->writeInt (chunkName ("fmt ")); output->writeInt (16); 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 (bytesPerFrame * (int) sampleRate); output->writeShort ((short) bytesPerFrame); output->writeShort ((short) bitsPerSample); 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()); } output->writeInt (chunkName ("data")); output->writeInt (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 = 0; return 0; } 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 0; } namespace { bool juce_slowCopyOfWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) { TemporaryFile tempFile (file); WavAudioFormat wav; ScopedPointer reader (wav.createReaderFor (file.createInputStream(), true)); if (reader != 0) { ScopedPointer outStream (tempFile.getFile().createOutputStream()); if (outStream != 0) { ScopedPointer writer (wav.createWriterFor (outStream, reader->sampleRate, reader->numChannels, reader->bitsPerSample, metadata, 0)); if (writer != 0) { outStream.release(); bool ok = writer->writeFromAudioReader (*reader, 0, -1); writer = 0; reader = 0; return ok && tempFile.overwriteTargetFileWithTemporary(); } } } return false; } } bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata) { ScopedPointer reader ((WavAudioFormatReader*) createReaderFor (wavFile.createInputStream(), true)); if (reader != 0) { const int64 bwavPos = reader->bwavChunkStart; const int64 bwavSize = reader->bwavSize; reader = 0; 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 juce_slowCopyOfWavFileWithNewMetadata (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_), deleteReader (deleteReaderWhenThisIsDeleted), nextPlayPos (0), looping (false) { jassert (reader != 0); } AudioFormatReaderSource::~AudioFormatReaderSource() { releaseResources(); if (deleteReader) delete reader; } void AudioFormatReaderSource::setNextReadPosition (int newPosition) { nextPlayPos = newPosition; } void AudioFormatReaderSource::setLooping (bool shouldLoop) { looping = shouldLoop; } int AudioFormatReaderSource::getNextReadPosition() const { return (looping) ? (nextPlayPos % (int) reader->lengthInSamples) : nextPlayPos; } int AudioFormatReaderSource::getTotalLength() const { return (int) reader->lengthInSamples; } void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) { } void AudioFormatReaderSource::releaseResources() { } void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { if (info.numSamples > 0) { const int start = nextPlayPos; if (looping) { const int newStart = start % (int) reader->lengthInSamples; const int newEnd = (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 (0), sampleRate (0), bufferSize (0), tempBuffer (2, 8), lastGain (1.0f), gain (1.0f) { } AudioSourcePlayer::~AudioSourcePlayer() { setSource (0); } void AudioSourcePlayer::setSource (AudioSource* newSource) { if (source != newSource) { AudioSource* const oldSource = source; if (newSource != 0 && bufferSize > 0 && sampleRate > 0) newSource->prepareToPlay (bufferSize, sampleRate); { const ScopedLock sl (readLock); source = newSource; } if (oldSource != 0) oldSource->releaseResources(); } } void AudioSourcePlayer::setGain (const float newGain) throw() { 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 != 0) { 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] != 0) { inputChans [numInputs++] = inputChannelData[i]; if (numInputs >= numElementsInArray (inputChans)) break; } } for (i = 0; i < totalNumOutputChannels; ++i) { if (outputChannelData[i] != 0) { 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] != 0) zeromem (outputChannelData[i], sizeof (float) * numSamples); } } void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) { sampleRate = device->getCurrentSampleRate(); bufferSize = device->getCurrentBufferSizeSamples(); zeromem (channels, sizeof (channels)); if (source != 0) source->prepareToPlay (bufferSize, sampleRate); } void AudioSourcePlayer::audioDeviceStopped() { if (source != 0) 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 (0), resamplerSource (0), bufferingSource (0), positionableSource (0), masterSource (0), 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 (0); releaseResources(); } void AudioTransportSource::setSource (PositionableAudioSource* const newSource, int readAheadBufferSize_, double sourceSampleRateToCorrectFor) { if (source == newSource) { if (source == 0) return; setSource (0, 0, 0); // deselect and reselect to avoid releasing resources wrongly } readAheadBufferSize = readAheadBufferSize_; sourceSampleRate = sourceSampleRateToCorrectFor; ResamplingAudioSource* newResamplerSource = 0; BufferingAudioSource* newBufferingSource = 0; PositionableAudioSource* newPositionableSource = 0; AudioSource* newMasterSource = 0; ScopedPointer oldResamplerSource (resamplerSource); ScopedPointer oldBufferingSource (bufferingSource); AudioSource* oldMasterSource = masterSource; if (newSource != 0) { newPositionableSource = newSource; if (readAheadBufferSize_ > 0) newPositionableSource = newBufferingSource = new BufferingAudioSource (newPositionableSource, false, readAheadBufferSize_); newPositionableSource->setNextReadPosition (0); if (sourceSampleRateToCorrectFor != 0) newMasterSource = newResamplerSource = new ResamplingAudioSource (newPositionableSource, false); else newMasterSource = newPositionableSource; if (isPrepared) { if (newResamplerSource != 0 && 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 != 0) oldMasterSource->releaseResources(); } void AudioTransportSource::start() { if ((! playing) && masterSource != 0) { { 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 (roundToInt (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 (int newPosition) { if (positionableSource != 0) { if (sampleRate > 0 && sourceSampleRate > 0) newPosition = roundToInt (newPosition * sourceSampleRate / sampleRate); positionableSource->setNextReadPosition (newPosition); } } int AudioTransportSource::getNextReadPosition() const { if (positionableSource != 0) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return roundToInt (positionableSource->getNextReadPosition() * ratio); } return 0; } int AudioTransportSource::getTotalLength() const { const ScopedLock sl (callbackLock); if (positionableSource != 0) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; return roundToInt (positionableSource->getTotalLength() * ratio); } return 0; } bool AudioTransportSource::isLooping() const { const ScopedLock sl (callbackLock); return positionableSource != 0 && positionableSource->isLooping(); } void AudioTransportSource::setGain (const float newGain) throw() { gain = newGain; } void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { const ScopedLock sl (callbackLock); sampleRate = sampleRate_; blockSize = samplesPerBlockExpected; if (masterSource != 0) masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); if (resamplerSource != 0 && sourceSampleRate != 0) resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); isPrepared = true; } void AudioTransportSource::releaseResources() { const ScopedLock sl (callbackLock); if (masterSource != 0) masterSource->releaseResources(); isPrepared = false; } void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (callbackLock); inputStreamEOF = false; if (masterSource != 0 && ! 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 != 0 && 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_, int numberOfSamplesToBuffer_) : source (source_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_), numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)), buffer (2, 0), bufferValidStart (0), bufferValidEnd (0), nextPlayPos (0), wasSourceLooping (false) { jassert (source_ != 0); 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 != 0) thread->removeSource (this); if (deleteSourceWhenDeleted) delete source; } void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) { source->prepareToPlay (samplesPerBlockExpected, sampleRate_); sampleRate = sampleRate_; buffer.setSize (2, 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 != 0) thread->removeSource (this); buffer.setSize (2, 0); source->releaseResources(); } void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { const ScopedLock sl (bufferStartPosLock); const int validStart = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos; const int validEnd = 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 (2, info.buffer->getNumChannels()); --chan >= 0;) { const int startBufferIndex = (validStart + nextPlayPos) % buffer.getNumSamples(); const int endBufferIndex = (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 != 0) thread->notify(); } int BufferingAudioSource::getNextReadPosition() const { return (source->isLooping() && nextPlayPos > 0) ? nextPlayPos % source->getTotalLength() : nextPlayPos; } void BufferingAudioSource::setNextReadPosition (int newPosition) { const ScopedLock sl (bufferStartPosLock); nextPlayPos = newPosition; SharedBufferingAudioSourceThread* const thread = SharedBufferingAudioSourceThread::getInstanceWithoutCreating(); if (thread != 0) thread->notify(); } bool BufferingAudioSource::readNextBufferChunk() { int newBVS, newBVE, sectionToReadStart, sectionToReadEnd; { const ScopedLock sl (bufferStartPosLock); if (wasSourceLooping != isLooping()) { wasSourceLooping = isLooping(); bufferValidStart = 0; bufferValidEnd = 0; } newBVS = jmax (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 (abs (newBVS - bufferValidStart) > 512 || abs (newBVE - bufferValidEnd) > 512) { newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); sectionToReadStart = bufferValidEnd; sectionToReadEnd = newBVE; bufferValidStart = newBVS; bufferValidEnd = jmin (bufferValidEnd, newBVE); } } if (sectionToReadStart != sectionToReadEnd) { const int bufferIndexStart = sectionToReadStart % buffer.getNumSamples(); const int bufferIndexEnd = sectionToReadEnd % buffer.getNumSamples(); if (bufferIndexStart < bufferIndexEnd) { readBufferSection (sectionToReadStart, sectionToReadEnd - sectionToReadStart, bufferIndexStart); } else { const int initialSize = buffer.getNumSamples() - bufferIndexStart; readBufferSection (sectionToReadStart, initialSize, bufferIndexStart); readBufferSection (sectionToReadStart + initialSize, (sectionToReadEnd - sectionToReadStart) - initialSize, 0); } const ScopedLock sl2 (bufferStartPosLock); bufferValidStart = newBVS; bufferValidEnd = newBVE; return true; } else { return false; } } void BufferingAudioSource::readBufferSection (int start, int length, 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_) : requiredNumberOfChannels (2), source (source_), deleteSourceWhenDeleted (deleteSourceWhenDeleted_), buffer (2, 16) { remappedInfo.buffer = &buffer; remappedInfo.startSample = 0; } ChannelRemappingAudioSource::~ChannelRemappingAudioSource() { if (deleteSourceWhenDeleted) delete source; } 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 (deleteInputWhenDeleted_) { jassert (inputSource != 0); for (int i = 2; --i >= 0;) iirFilters.add (new IIRFilter()); } IIRFilterAudioSource::~IIRFilterAudioSource() { if (deleteInputWhenDeleted) delete input; } 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_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 != 0 && ! 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 != 0) { 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 (deleteInputWhenDeleted_), ratio (1.0), lastRatio (1.0), buffer (numChannels_, 0), sampsInBuffer (0), numChannels (numChannels_) { jassert (input != 0); } ResamplingAudioSource::~ResamplingAudioSource() { if (deleteInputWhenDeleted) delete input; } void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) { jassert (samplesInPerOutputSample > 0); const ScopedLock sl (ratioLock); ratio = jmax (0.0, samplesInPerOutputSample); } void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { const ScopedLock 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 ScopedLock 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() { zeromem (filterStates, sizeof (FilterState) * 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() : currentAudioDevice (0), numInputChansNeeded (0), numOutputChansNeeded (2), listNeedsScanning (true), useInputNames (false), inputLevelMeasurementEnabledCount (0), inputLevel (0), tempBuffer (2, 2), defaultMidiOutput (0), cpuUsageMs (0), timeToCpuScale (0) { callbackHandler.owner = this; } AudioDeviceManager::~AudioDeviceManager() { currentAudioDevice = 0; defaultMidiOutput = 0; } 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; } AudioIODeviceType* juce_createAudioIODeviceType_CoreAudio(); AudioIODeviceType* juce_createAudioIODeviceType_iPhoneAudio(); AudioIODeviceType* juce_createAudioIODeviceType_WASAPI(); AudioIODeviceType* juce_createAudioIODeviceType_DirectSound(); AudioIODeviceType* juce_createAudioIODeviceType_ASIO(); AudioIODeviceType* juce_createAudioIODeviceType_ALSA(); AudioIODeviceType* juce_createAudioIODeviceType_JACK(); void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & list) { (void) list; // (to avoid 'unused param' warnings) #if JUCE_WINDOWS #if JUCE_WASAPI if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) list.add (juce_createAudioIODeviceType_WASAPI()); #endif #if JUCE_DIRECTSOUND list.add (juce_createAudioIODeviceType_DirectSound()); #endif #if JUCE_ASIO list.add (juce_createAudioIODeviceType_ASIO()); #endif #endif #if JUCE_MAC list.add (juce_createAudioIODeviceType_CoreAudio()); #endif #if JUCE_IOS list.add (juce_createAudioIODeviceType_iPhoneAudio()); #endif #if JUCE_LINUX && JUCE_ALSA list.add (juce_createAudioIODeviceType_ALSA()); #endif #if JUCE_LINUX && JUCE_JACK list.add (juce_createAudioIODeviceType_JACK()); #endif } 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 != 0 && e->hasTagName ("DEVICESETUP")) { lastExplicitSettings = new XmlElement (*e); String error; AudioDeviceSetup setup; if (preferredSetupOptions != 0) 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 != 0) 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 != 0) { 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 != 0) { 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 != 0 ? new XmlElement (*lastExplicitSettings) : 0; } 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 0; } void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { setup = currentSetup; } void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = 0; 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 != 0) 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 == 0 || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) { deleteCurrentDevice(); if (treatAsChosenDevice) updateXml(); return String::empty; } if (currentSetup.inputDeviceName != newInputDeviceName || currentSetup.outputDeviceName != newOutputDeviceName || currentAudioDevice == 0) { 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 == 0) 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 != 0); 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 == 0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 > 0.0) return lowestAbove44; return currentAudioDevice->getSampleRate (0); } int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const { jassert (currentAudioDevice != 0); 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 != 0) currentAudioDevice->stop(); testSound = 0; } void AudioDeviceManager::closeAudioDevice() { stopDevice(); currentAudioDevice = 0; } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == 0) { 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 != 0) { 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 != 0 && newCallback != 0) newCallback->audioDeviceAboutToStart (currentAudioDevice); const ScopedLock sl (audioCallbackLock); callbacks.add (newCallback); } void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) { if (callbackToRemove != 0) { bool needsDeinitialising = currentAudioDevice != 0; { 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) { 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; } } 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 != 0 && dst != 0) 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 != 0) { 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 = 0; } } 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 min = MidiInput::openDevice (index, &callbackHandler); if (min != 0) { enabledMidiInputs.add (min); min->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()) { midiCallbacks.add (callbackToAdd); midiCallbackDevices.add (0); } else { for (int i = enabledMidiInputs.size(); --i >= 0;) { if (enabledMidiInputs[i]->getName() == name) { const ScopedLock sl (midiCallbackLock); midiCallbacks.add (callbackToAdd); midiCallbackDevices.add (enabledMidiInputs[i]); break; } } } } void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* /*callback*/) { const ScopedLock sl (midiCallbackLock); for (int i = midiCallbacks.size(); --i >= 0;) { String devName; if (midiCallbackDevices.getUnchecked(i) != 0) devName = midiCallbackDevices.getUnchecked(i)->getName(); if (devName == name) { midiCallbacks.remove (i); midiCallbackDevices.remove (i); } } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const bool isDefaultSource = (source == 0 || source == enabledMidiInputs.getFirst()); const ScopedLock sl (midiCallbackLock); for (int i = midiCallbackDevices.size(); --i >= 0;) { MidiInput* const md = midiCallbackDevices.getUnchecked(i); if (md == source || (md == 0 && isDefaultSource)) midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); } } } void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) { if (defaultMidiOutputName != deviceName) { SortedSet oldCallbacks; { const ScopedLock sl (audioCallbackLock); oldCallbacks = callbacks; callbacks.clear(); } if (currentAudioDevice != 0) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceStopped(); defaultMidiOutput = 0; defaultMidiOutputName = deviceName; if (deviceName.isNotEmpty()) defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); if (currentAudioDevice != 0) 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 != 0) { 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; } 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() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioIODeviceType.cpp ***/ /*** Start of inlined file: juce_MidiOutput.cpp ***/ BEGIN_JUCE_NAMESPACE MidiOutput::MidiOutput() : Thread ("midi out"), internal (0), firstMessage (0) { } MidiOutput::PendingMessage::PendingMessage (const uint8* const data, const int len, const double sampleNumber) : message (data, len, sampleNumber) { } 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 == 0 || firstMessage->message.getTimeStamp() > eventTime) { m->next = firstMessage; firstMessage = m; } else { PendingMessage* mm = firstMessage; while (mm->next != 0 && 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 != 0) { 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 != 0) { eventTime = roundToInt (message->message.getTimeStamp()); if (eventTime > now + 20) { timeToWait = eventTime - (now + 20); message = 0; } else { firstMessage = message->next; } } } if (message != 0) { 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_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) zerostruct (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"); Test1 ::test (*this); Test1 ::test (*this); Test1 ::test (*this); Test1 ::test (*this); 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) throw() : numChannels (numChannels_), size (numSamples) { jassert (numSamples >= 0); jassert (numChannels_ > 0); allocateData(); } AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) throw() : 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) throw() : numChannels (numChannels_), size (numSamples), allocatedBytes (0) { jassert (numChannels_ > 0); allocateChannels (dataToReferTo); } void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, const int newNumChannels, const int newNumSamples) throw() { jassert (newNumChannels > 0); allocatedBytes = 0; allocatedData.free(); numChannels = newNumChannels; size = newNumSamples; allocateChannels (dataToReferTo); } void AudioSampleBuffer::allocateChannels (float** const dataToReferTo) { // (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] != 0); channels[i] = dataToReferTo[i]; } channels [numChannels] = 0; } AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) throw() { 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() throw() { } void AudioSampleBuffer::setSize (const int newNumChannels, const int newNumSamples, const bool keepExistingContent, const bool clearExtraSpace, const bool avoidReallocating) throw() { 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) zeromem (allocatedData, 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() throw() { for (int i = 0; i < numChannels; ++i) zeromem (channels[i], size * sizeof (float)); } void AudioSampleBuffer::clear (const int startSample, const int numSamples) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); 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) throw() { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); 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) throw() { 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) throw() { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); 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) throw() { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); 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) throw() { jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); 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 throw() { 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 throw() { 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 throw() { 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 throw() { 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 int readerStartSample, const bool useLeftChan, const bool useRightChan) { jassert (reader != 0); 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] = 0; } else if (useRightChan) { chans[0] = 0; chans[1] = reinterpret_cast (getSampleData (0, startSample)); } chans[2] = 0; 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 != 0) { 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] == 0 || chans[1] == 0)) { // 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 != 0); 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() throw() { const ScopedLock sl (processLock); x1 = 0; x2 = 0; y1 = 0; y2 = 0; } float IIRFilter::processSingleSampleRaw (const float in) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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() throw() { const ScopedLock sl (processLock); active = false; } void IIRFilter::copyCoefficientsFrom (const IIRFilter& other) throw() { 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) throw() { 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_MidiBuffer.cpp ***/ BEGIN_JUCE_NAMESPACE MidiBuffer::MidiBuffer() throw() : bytesUsed (0) { } MidiBuffer::MidiBuffer (const MidiMessage& message) throw() : bytesUsed (0) { addEvent (message, 0); } MidiBuffer::MidiBuffer (const MidiBuffer& other) throw() : data (other.data), bytesUsed (other.bytesUsed) { } MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) throw() { bytesUsed = other.bytesUsed; data = other.data; return *this; } void MidiBuffer::swapWith (MidiBuffer& other) throw() { data.swapWith (other.data); swapVariables (bytesUsed, other.bytesUsed); } MidiBuffer::~MidiBuffer() { } inline uint8* MidiBuffer::getData() const throw() { return static_cast (data.getData()); } inline int MidiBuffer::getEventTime (const void* const d) throw() { return *static_cast (d); } inline uint16 MidiBuffer::getEventDataSize (const void* const d) throw() { return *reinterpret_cast (static_cast (d) + sizeof (int)); } inline uint16 MidiBuffer::getEventTotalSize (const void* const d) throw() { return getEventDataSize (d) + sizeof (int) + sizeof (uint16); } void MidiBuffer::clear() throw() { 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) throw() { 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 throw() { return bytesUsed == 0; } int MidiBuffer::getNumEvents() const throw() { 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 throw() { return bytesUsed > 0 ? getEventTime (data.getData()) : 0; } int MidiBuffer::getLastEventTime() const throw() { 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 throw() { const uint8* const endData = getData() + bytesUsed; while (d < endData && getEventTime (d) <= samplePosition) d += getEventTotalSize (d); return d; } MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) throw() : buffer (buffer_), data (buffer_.getData()) { } MidiBuffer::Iterator::~Iterator() throw() { } void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); if (diff == 0) { if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; else if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; else return 0; } else { return (diff > 0) ? 1 : -1; } } }; } MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) { } MidiFile::~MidiFile() { clear(); } void MidiFile::clear() { tracks.clear(); } int MidiFile::getNumTracks() const throw() { return tracks.size(); } const MidiMessageSequence* MidiFile::getTrack (const int index) const throw() { return tracks [index]; } void MidiFile::addTrack (const MidiMessageSequence& trackSequence) { tracks.add (new MidiMessageSequence (trackSequence)); } short MidiFile::getTimeFormat() const throw() { return timeFormat; } void MidiFile::setTicksPerQuarterNote (const int ticks) throw() { timeFormat = (short) ticks; } void MidiFile::setSmpteTimeFormat (const int framesPerSecond, const int subframeResolution) throw() { 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 (size < 0) return false; 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.write (out.getData(), (int) out.getDataSize()); } 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 throw() { 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 throw() { 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 int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) throw() { 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) throw() { // 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() throw() : timeStamp (0), data (static_cast (preallocatedData.asBytes)), size (1) { data[0] = 0xfe; } 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) throw() : 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) throw() : 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) throw() : 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 throw() { if ((data[0] & 0xf0) != 0xf0) return (data[0] & 0xf) + 1; else return 0; } bool MidiMessage::isForChannel (const int channel) const throw() { 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) throw() { 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 throw() { return ((data[0] & 0xf0) == 0x90) && (returnTrueForVelocity0 || data[2] != 0); } bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const throw() { return ((data[0] & 0xf0) == 0x80) || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); } bool MidiMessage::isNoteOnOrOff() const throw() { const int d = data[0] & 0xf0; return (d == 0x90) || (d == 0x80); } int MidiMessage::getNoteNumber() const throw() { return data[1]; } void MidiMessage::setNoteNumber (const int newNoteNumber) throw() { if (isNoteOnOrOff()) data[1] = (uint8) jlimit (0, 127, newNoteNumber); } uint8 MidiMessage::getVelocity() const throw() { if (isNoteOnOrOff()) return data[2]; else return 0; } float MidiMessage::getFloatVelocity() const throw() { return getVelocity() * (1.0f / 127.0f); } void MidiMessage::setVelocity (const float newVelocity) throw() { if (isNoteOnOrOff()) data[2] = (uint8) jlimit (0, 0x7f, roundToInt (newVelocity * 127.0f)); } void MidiMessage::multiplyVelocity (const float scaleFactor) throw() { if (isNoteOnOrOff()) data[2] = (uint8) jlimit (0, 0x7f, roundToInt (scaleFactor * data[2])); } bool MidiMessage::isAftertouch() const throw() { return (data[0] & 0xf0) == 0xa0; } int MidiMessage::getAfterTouchValue() const throw() { return data[2]; } const MidiMessage MidiMessage::aftertouchChange (const int channel, const int noteNum, const int aftertouchValue) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (noteNum, (int) 128)); jassert (isPositiveAndBelow (aftertouchValue, (int) 128)); return MidiMessage (0xa0 | jlimit (0, 15, channel - 1), noteNum & 0x7f, aftertouchValue & 0x7f); } bool MidiMessage::isChannelPressure() const throw() { return (data[0] & 0xf0) == 0xd0; } int MidiMessage::getChannelPressureValue() const throw() { jassert (isChannelPressure()); return data[1]; } const MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (pressure, (int) 128)); return MidiMessage (0xd0 | jlimit (0, 15, channel - 1), pressure & 0x7f); } bool MidiMessage::isProgramChange() const throw() { return (data[0] & 0xf0) == 0xc0; } int MidiMessage::getProgramChangeNumber() const throw() { return data[1]; } const MidiMessage MidiMessage::programChange (const int channel, const int programNumber) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 return MidiMessage (0xc0 | jlimit (0, 15, channel - 1), programNumber & 0x7f); } bool MidiMessage::isPitchWheel() const throw() { return (data[0] & 0xf0) == 0xe0; } int MidiMessage::getPitchWheelValue() const throw() { return data[1] | (data[2] << 7); } const MidiMessage MidiMessage::pitchWheel (const int channel, const int position) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 jassert (isPositiveAndBelow (position, (int) 0x4000)); return MidiMessage (0xe0 | jlimit (0, 15, channel - 1), position & 127, (position >> 7) & 127); } bool MidiMessage::isController() const throw() { return (data[0] & 0xf0) == 0xb0; } int MidiMessage::getControllerNumber() const throw() { jassert (isController()); return data[1]; } int MidiMessage::getControllerValue() const throw() { jassert (isController()); return data[2]; } const MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) throw() { // the channel must be between 1 and 16 inclusive jassert (channel > 0 && channel <= 16); return MidiMessage (0xb0 | jlimit (0, 15, channel - 1), controllerType & 127, value & 127); } const MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) throw() { return noteOn (channel, noteNumber, (uint8)(velocity * 127.0f)); } const MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) throw() { jassert (channel > 0 && channel <= 16); jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x90 | jlimit (0, 15, channel - 1), noteNumber & 127, jlimit (0, 127, roundToInt (velocity))); } const MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) throw() { jassert (channel > 0 && channel <= 16); jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x80 | jlimit (0, 15, channel - 1), noteNumber & 127, 0); } const MidiMessage MidiMessage::allNotesOff (const int channel) throw() { return controllerEvent (channel, 123, 0); } bool MidiMessage::isAllNotesOff() const throw() { return (data[0] & 0xf0) == 0xb0 && data[1] == 123; } const MidiMessage MidiMessage::allSoundOff (const int channel) throw() { return controllerEvent (channel, 120, 0); } bool MidiMessage::isAllSoundOff() const throw() { return (data[0] & 0xf0) == 0xb0 && data[1] == 120; } const MidiMessage MidiMessage::allControllersOff (const int channel) throw() { return controllerEvent (channel, 121, 0); } const MidiMessage MidiMessage::masterVolume (const float volume) { const int vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); uint8 buf[8]; buf[0] = 0xf0; buf[1] = 0x7f; buf[2] = 0x7f; buf[3] = 0x04; buf[4] = 0x01; buf[5] = (uint8) (vol & 0x7f); buf[6] = (uint8) (vol >> 7); buf[7] = 0xf7; return MidiMessage (buf, 8); } bool MidiMessage::isSysEx() const throw() { return *data == 0xf0; } const MidiMessage MidiMessage::createSysExMessage (const uint8* sysexData, const int dataSize) { MemoryBlock mm (dataSize + 2); uint8* const m = static_cast (mm.getData()); m[0] = 0xf0; memcpy (m + 1, sysexData, dataSize); m[dataSize + 1] = 0xf7; return MidiMessage (m, dataSize + 2); } const uint8* MidiMessage::getSysExData() const throw() { return (isSysEx()) ? getRawData() + 1 : 0; } int MidiMessage::getSysExDataSize() const throw() { return (isSysEx()) ? size - 2 : 0; } bool MidiMessage::isMetaEvent() const throw() { return *data == 0xff; } bool MidiMessage::isActiveSense() const throw() { return *data == 0xfe; } int MidiMessage::getMetaEventType() const throw() { if (*data != 0xff) return -1; else return data[1]; } int MidiMessage::getMetaEventLength() const throw() { if (*data == 0xff) { int n; return jmin (size - 2, readVariableLengthVal (data + 2, n)); } return 0; } const uint8* MidiMessage::getMetaEventData() const throw() { int n; const uint8* d = data + 2; readVariableLengthVal (d, n); return d + n; } bool MidiMessage::isTrackMetaEvent() const throw() { return getMetaEventType() == 0; } bool MidiMessage::isEndOfTrackMetaEvent() const throw() { return getMetaEventType() == 47; } bool MidiMessage::isTextMetaEvent() const throw() { const int t = getMetaEventType(); return t > 0 && t < 16; } const String MidiMessage::getTextFromTextMetaEvent() const { return String (reinterpret_cast (getMetaEventData()), getMetaEventLength()); } bool MidiMessage::isTrackNameEvent() const throw() { return (data[1] == 3) && (*data == 0xff); } bool MidiMessage::isTempoMetaEvent() const throw() { return (data[1] == 81) && (*data == 0xff); } bool MidiMessage::isMidiChannelMetaEvent() const throw() { return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } int MidiMessage::getMidiChannelMetaEventChannel() const throw() { return data[3] + 1; } double MidiMessage::getTempoSecondsPerQuarterNote() const throw() { 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 throw() { 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); } } const MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) throw() { uint8 d[8]; d[0] = 0xff; d[1] = 81; d[2] = 3; d[3] = (uint8) (microsecondsPerQuarterNote >> 16); d[4] = (uint8) ((microsecondsPerQuarterNote >> 8) & 0xff); d[5] = (uint8) (microsecondsPerQuarterNote & 0xff); return MidiMessage (d, 6, 0.0); } bool MidiMessage::isTimeSignatureMetaEvent() const throw() { return (data[1] == 0x58) && (*data == (uint8) 0xff); } void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const throw() { if (isTimeSignatureMetaEvent()) { const uint8* const d = getMetaEventData(); numerator = d[0]; denominator = 1 << d[1]; } else { numerator = 4; denominator = 4; } } const MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) { uint8 d[8]; d[0] = 0xff; d[1] = 0x58; d[2] = 0x04; d[3] = (uint8) numerator; int n = 1; int powerOfTwo = 0; while (n < denominator) { n <<= 1; ++powerOfTwo; } d[4] = (uint8) powerOfTwo; d[5] = 0x01; d[6] = 96; return MidiMessage (d, 7, 0.0); } const MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) throw() { uint8 d[8]; d[0] = 0xff; d[1] = 0x20; d[2] = 0x01; d[3] = (uint8) jlimit (0, 0xff, channel - 1); return MidiMessage (d, 4, 0.0); } bool MidiMessage::isKeySignatureMetaEvent() const throw() { return getMetaEventType() == 89; } int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const throw() { return (int) *getMetaEventData(); } const MidiMessage MidiMessage::endOfTrack() throw() { return MidiMessage (0xff, 0x2f, 0, 0.0); } bool MidiMessage::isSongPositionPointer() const throw() { return *data == 0xf2; } int MidiMessage::getSongPositionPointerMidiBeat() const throw() { return data[1] | (data[2] << 7); } const MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) throw() { return MidiMessage (0xf2, positionInMidiBeats & 127, (positionInMidiBeats >> 7) & 127); } bool MidiMessage::isMidiStart() const throw() { return *data == 0xfa; } const MidiMessage MidiMessage::midiStart() throw() { return MidiMessage (0xfa); } bool MidiMessage::isMidiContinue() const throw() { return *data == 0xfb; } const MidiMessage MidiMessage::midiContinue() throw() { return MidiMessage (0xfb); } bool MidiMessage::isMidiStop() const throw() { return *data == 0xfc; } const MidiMessage MidiMessage::midiStop() throw() { return MidiMessage (0xfc); } bool MidiMessage::isMidiClock() const throw() { return *data == 0xf8; } const MidiMessage MidiMessage::midiClock() throw() { return MidiMessage (0xf8); } bool MidiMessage::isQuarterFrame() const throw() { return *data == 0xf1; } int MidiMessage::getQuarterFrameSequenceNumber() const throw() { return ((int) data[1]) >> 4; } int MidiMessage::getQuarterFrameValue() const throw() { return ((int) data[1]) & 0x0f; } const MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) throw() { return MidiMessage (0xf1, (sequenceNumber << 4) | value); } bool MidiMessage::isFullFrame() const throw() { 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 throw() { jassert (isFullFrame()); timecodeType = (SmpteTimecodeType) (data[5] >> 5); hours = data[5] & 0x1f; minutes = data[6]; seconds = data[7]; frames = data[8]; } const MidiMessage MidiMessage::fullFrame (const int hours, const int minutes, const int seconds, const int frames, MidiMessage::SmpteTimecodeType timecodeType) { uint8 d[10]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x7f; d[3] = 0x01; d[4] = 0x01; d[5] = (uint8) ((hours & 0x01f) | (timecodeType << 5)); d[6] = (uint8) minutes; d[7] = (uint8) seconds; d[8] = (uint8) frames; d[9] = 0xf7; return MidiMessage (d, 10, 0.0); } bool MidiMessage::isMidiMachineControlMessage() const throw() { return data[0] == 0xf0 && data[1] == 0x7f && data[3] == 0x06 && size > 5; } MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const throw() { jassert (isMidiMachineControlMessage()); return (MidiMachineControlCommand) data[4]; } const MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) { uint8 d[6]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x00; d[3] = 0x06; d[4] = (uint8) command; d[5] = 0xf7; return MidiMessage (d, 6, 0.0); } bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const throw() { 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; } const MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) { uint8 d[12]; d[0] = 0xf0; d[1] = 0x7f; d[2] = 0x00; d[3] = 0x06; d[4] = 0x44; d[5] = 0x06; d[6] = 0x01; d[7] = (uint8) hours; d[8] = (uint8) minutes; d[9] = (uint8) seconds; d[10] = (uint8) frames; d[11] = 0xf7; return MidiMessage (d, 12, 0.0); } const 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) throw() { noteNumber -= 12 * 6 + 9; // now 0 = A return frequencyOfA * pow (2.0, noteNumber / 12.0); } const 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; } const 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; } const 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*) 0; } const 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*) 0; } 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) throw() { 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 != 0 && meh->noteOffObject != 0) return meh->noteOffObject->message.getTimeStamp(); else return 0.0; } int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const { const MidiEventHolder* const meh = list [index]; return (meh != 0) ? 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) throw() { 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 = 0; 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 (0) { } 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() throw() { } 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]) == 0); #endif #if JUCE_PLUGINHOST_AU && JUCE_MAC jassert (dynamic_cast (formats[i]) == 0); #endif #if JUCE_PLUGINHOST_DX && JUCE_WINDOWS jassert (dynamic_cast (formats[i]) == 0); #endif #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX jassert (dynamic_cast (formats[i]) == 0); #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 = 0; for (int i = 0; i < formats.size(); ++i) { result = formats.getUnchecked(i)->createInstanceFromDescription (description); if (result != 0) break; } if (result == 0) { 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 0; } 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 0; } 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 0; } 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) throw() { 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) != 0) { 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 != 0); 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) { bool loaded = false; for (int j = 0; j < AudioPluginFormatManager::getInstance()->getNumFormats(); ++j) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (j); if (scanAndAddFile (files[i], true, typesFound, *format)) loaded = true; } if (! loaded) { 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() throw() {} 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(); } const 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); changeListenerCallback (0); } 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*) { 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 != 0) { 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::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..."); } const int r = menu.showAt (&optionsButton); if (r == 1) { list.clear(); } else if (r == 2) { list.sort (KnownPluginList::sortAlphabetically); } else if (r == 3) { list.sort (KnownPluginList::sortByCategory); } else if (r == 4) { list.sort (KnownPluginList::sortByManufacturer); } else if (r == 5) { const SparseSet selected (listBox.getSelectedRows()); for (int i = list.getNumTypes(); --i >= 0;) if (selected.contains (i)) list.removeType (i); } else if (r == 6) { const PluginDescription* const desc = list.getType (listBox.getSelectedRow()); if (desc != 0) { if (File (desc->fileOrIdentifier).existsAsFile()) File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); } } else if (r == 7) { for (int i = list.getNumTypes(); --i >= 0;) { if (! AudioPluginFormatManager::getInstance()->doesPluginStillExist (*list.getType (i))) { list.removeType (i); } } } else if (r != 0) { typeToScan = r - 10; startTimer (1); } } } 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 (format == 0) return; FileSearchPath path (format->getDefaultLocationsToSearch()); if (propertiesToUse != 0) 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 != 0) { 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 (", ")); } } 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) { char s[4]; s[0] = (char) (((uint32) type) >> 24); s[1] = (char) (((uint32) type) >> 16); s[2] = (char) (((uint32) type) >> 8); s[3] = (char) ((uint32) type); 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); 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(); void getParameterListFromPlugin(); 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 (0), 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() { getParameterListFromPlugin(); setPluginCallbacks(); int numIns, numOuts; getNumChannels (numIns, numOuts); setPlayConfigDetails (numIns, numOuts, 0, 0); setLatencySamples (0); } void AudioUnitPluginInstance::getParameterListFromPlugin() { 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, ¶meterIds.getReference(0), ¶mListSize); } } } void AudioUnitPluginInstance::setPluginCallbacks() { if (audioUnit != 0) { { AURenderCallbackStruct info; zerostruct (info); info.inputProcRefCon = this; info.inputProc = renderGetInputCallback; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &info, sizeof (info)); } { HostCallbackInfo info; zerostruct (info); 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; zerostruct (stream); 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 != 0 && ph->getCurrentPosition (result)) { if (outCurrentBeat != 0) *outCurrentBeat = result.ppqPosition; if (outCurrentTempo != 0) *outCurrentTempo = result.bpm; } else { if (outCurrentBeat != 0) *outCurrentBeat = 0; if (outCurrentTempo != 0) *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 != 0 && ph->getCurrentPosition (result)) { if (outTimeSig_Numerator != 0) *outTimeSig_Numerator = result.timeSigNumerator; if (outTimeSig_Denominator != 0) *outTimeSig_Denominator = result.timeSigDenominator; if (outDeltaSampleOffsetToNextBeat != 0) *outDeltaSampleOffsetToNextBeat = 0; //xxx if (outCurrentMeasureDownBeat != 0) *outCurrentMeasureDownBeat = result.ppqPositionOfLastBarStart; //xxx wrong } else { if (outDeltaSampleOffsetToNextBeat != 0) *outDeltaSampleOffsetToNextBeat = 0; if (outTimeSig_Numerator != 0) *outTimeSig_Numerator = 4; if (outTimeSig_Denominator != 0) *outTimeSig_Denominator = 4; if (outCurrentMeasureDownBeat != 0) *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 != 0 && ph->getCurrentPosition (result)) { if (outIsPlaying != 0) *outIsPlaying = result.isPlaying; if (outTransportStateChanged != 0) { *outTransportStateChanged = result.isPlaying != wasPlaying; wasPlaying = result.isPlaying; } if (outCurrentSampleInTimeLine != 0) *outCurrentSampleInTimeLine = roundToInt (result.timeInSeconds * getSampleRate()); if (outIsCycling != 0) *outIsCycling = false; if (outCycleStartBeat != 0) *outCycleStartBeat = 0; if (outCycleEndBeat != 0) *outCycleEndBeat = 0; } else { if (outIsPlaying != 0) *outIsPlaying = false; if (outTransportStateChanged != 0) *outTransportStateChanged = false; if (outCurrentSampleInTimeLine != 0) *outCurrentSampleInTimeLine = 0; if (outIsCycling != 0) *outIsCycling = false; if (outCycleStartBeat != 0) *outCycleStartBeat = 0; if (outCycleEndBeat != 0) *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 = 0; 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 != 0) { timerCallback(); startTimer (70); } return pluginView != 0; } }; #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 throw() { 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 == 0) { w = new AudioUnitPluginWindowCarbon (*this); if (! static_cast (static_cast (w))->isValid()) w = 0; } #endif if (w == 0) w = new AudioUnitPluginWindowCocoa (*this, true); // use AUGenericView as a fallback return w.release(); } const String AudioUnitPluginInstance::getCategory() const { const char* result = 0; 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; zerostruct (info); 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_FactoryPresets, 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 != 0 && 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 != 0) { 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 0; } const StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath& /*directoriesToSearch*/, const bool /*recursive*/) { StringArray result; ComponentRecord* comp = 0; ComponentDescription desc; zerostruct (desc); for (;;) { zerostruct (desc); 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) #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 != 0) 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 != 0) { 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 == 0) events.calloc (size, 1); else events.realloc (size, 1); for (int i = numEventsAllocated; i < numEventsNeeded; ++i) { VstMidiEvent* const e = (VstMidiEvent*) juce_calloc (jmax ((int) sizeof (VstMidiEvent), (int) sizeof (VstMidiSysexEvent))); e->type = kVstMidiType; e->byteSize = sizeof (VstMidiEvent); events->events[i] = (VstEvent*) e; } numEventsAllocated = numEventsNeeded; } } void freeEvents() { if (events != 0) { for (int i = numEventsAllocated; --i >= 0;) { VstMidiEvent* const e = (VstMidiEvent*) (events->events[i]); if (e->type == kVstSysExType) delete[] (((VstMidiSysexEvent*) e)->sysexDump); juce_free (e); } events.free(); numEventsUsed = 0; numEventsAllocated = 0; } } HeapBlock events; private: int numEventsUsed, numEventsAllocated; }; #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) throw() { #ifdef JUCE_LITTLE_ENDIAN return (long) ByteOrder::swap ((uint32) x); #else return x; #endif } float vst_swapFloat (const float x) throw() { #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 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) throw() { 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) ++insideVSTCallback; shellUIDToCreate = 0; log ("Attempting to load VST: " + file.getFullPathName()); ScopedPointer m (new ModuleHandle (file)); if (! m->open()) m = 0; --insideVSTCallback; _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(); 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(), &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 0; 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]; zerostruct (buffer); 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]; zerostruct (buffer); dispatch (effGetVendorString, 0, 0, buffer, 0); desc.manufacturerName = buffer; } desc.version = getVersion(); desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); desc.isInstrument = (effect != 0 && (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 != 0 && (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 != 0 ? 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 != 0 ? 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 throw() { return effect != 0 && (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 throw() { return effect != 0 ? 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 (0), wantsMidiMessages (false), initialised (false), isPowerOn (false), tempBuffer (1, 1), module (module_) { try { _fpreset(); ++insideVSTCallback; name = module->pluginName; 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 = 0; if (audioMasterCoerced == 0) audioMasterCoerced = NewCFMFromMachO ((void*) &audioMaster); effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); } else #endif #endif { effect = module->moduleMain (&audioMaster); } --insideVSTCallback; if (effect != 0 && 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 = 0; } } catch (...) { --insideVSTCallback; } } VSTPluginInstance::~VSTPluginInstance() { const ScopedLock sl (lock); jassert (insideVSTCallback == 0); if (effect != 0 && 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 = 0; effect = 0; } 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 != 0) { 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 != 0) { 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), wasShowing (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 = 0; #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() != 0) { 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 (Component&) { const bool isShowingNow = isShowing(); if (wasShowing != isShowingNow) { wasShowing = isShowingNow; if (isShowingNow) 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 != 0) { 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; zerostruct (ev); 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; zerostruct (ev); 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, wasShowing, 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 = 0; 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 != 0) { 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 = 0; 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 != 0) { 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 != 0) { 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; zerostruct (ev); 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; zerostruct (ev); 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; zerostruct (ev); 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; zerostruct (ev); 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; zerostruct (ev); 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; zerostruct (ev); 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 = 0; 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 != 0) { 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 0; } 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().copyToCString (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().copyToCString (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 = 0; const int bytes = dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); if (data != 0 && 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 { const ScopedLock sl (lock); ++insideVSTCallback; int result = 0; try { if (effect != 0) { #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 --insideVSTCallback; return result; } } catch (...) { } --insideVSTCallback; 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() != 0) hostName = JUCEApplication::getInstance()->getApplicationName(); hostName.copyToCString ((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()) { ++insideVSTCallback; #if JUCE_MAC if (getActiveEditor() != 0) dispatch (effEditIdle, 0, 0, 0, 0); #endif juce_callAnyTimersSynchronously(); handleUpdateNowIfNeeded(); for (int i = ComponentPeer::getNumPeers(); --i >= 0;) ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); --insideVSTCallback; } break; case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; case audioMasterTempoAt: // returns (10000 * bpm) break; case audioMasterNeedIdle: startTimer (50); break; case audioMasterSizeWindow: if (getActiveEditor() != 0) 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(); #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 != 0 && 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 != 0 ? effect->uniqueID : 0; if (uid == 0) uid = module->file.hashCode(); return uid; } const String VSTPluginInstance::getCategory() const { const char* result = 0; 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 != 0 && 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 != 0 && 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 != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamName, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterLabel (int index) const { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamLabel, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } const String VSTPluginInstance::getParameterText (int index) { if (effect != 0) { jassert (index >= 0 && index < effect->numParams); char nm [256]; zerostruct (nm); dispatch (effGetParamDisplay, index, 0, nm, 0); return String (nm).trim(); } return String::empty; } bool VSTPluginInstance::isParameterAutomatable (int index) const { if (effect != 0) { 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().copyToCString ((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 != 0) { char nm [256]; zerostruct (nm); 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).toCString(), 0.0f); } else { jassertfalse; // xxx not implemented! } } void VSTPluginInstance::updateStoredProgramNames() { if (effect != 0 && getNumPrograms() > 0) { char nm [256]; zerostruct (nm); // 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 != 0) { char nm [256]; zerostruct (nm); 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)); ++insideVSTCallback; instance->dispatch (effOpen, 0, 0, 0, 0); --insideVSTCallback; } else { // It's a shell plugin, so iterate all the subtypes... char shellEffectName [64]; for (;;) { zerostruct (shellEffectName); 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 != 0) { shellUIDToCreate = desc.uid; result = new VSTPluginInstance (module); if (result->effect != 0) { result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) (VSTPluginInstance*) result; result->initialise(); } else { result = 0; } } 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 (0), 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 == 0); #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) throw() { 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_) throw() { numInputChannels = numIns; numOutputChannels = numOuts; sampleRate = sampleRate_; blockSize = blockSize_; } void AudioProcessor::setNonRealtime (const bool nonRealtime_) throw() { 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 != 0) 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 != 0) 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 != 0) 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 != 0) 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) throw() { const ScopedLock sl (callbackLock); if (activeEditor == editor) activeEditor = 0; } AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() { if (activeEditor != 0) return activeEditor; AudioProcessorEditor* const ed = createEditor(); // You must make your hasEditor() method return a consistent result! jassert (hasEditor() == (ed != 0)); if (ed != 0) { // 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 0; } void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const throw() { 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 throw() { 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 != 0); } 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; AudioProcessorGraph::Node::Node (const uint32 id_, AudioProcessor* const processor_) : id (id_), processor (processor_), isPrepared (false) { jassert (processor_ != 0); } AudioProcessorGraph::Node::~Node() { } 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 != 0) 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)->id == nodeId) return nodes.getUnchecked(i); return 0; } AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) { if (newProcessor == 0) { jassertfalse; return 0; } if (nodeId == 0) { nodeId = ++lastNodeId; } else { // you can't add a node with an id that already exists in the graph.. jassert (getNodeForId (nodeId) == 0); removeNode (nodeId); } 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 != 0) ioProc->setParentGraph (this); return n; } bool AudioProcessorGraph::removeNode (const uint32 nodeId) { disconnectNode (nodeId); for (int i = nodes.size(); --i >= 0;) { if (nodes.getUnchecked(i)->id == nodeId) { AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (static_cast (nodes.getUnchecked(i)->processor)); if (ioProc != 0) ioProc->setParentGraph (0); 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 { 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) { return c; } } return 0; } 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 == 0 || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getNumOutputChannels()) || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) return false; const Node* const dest = getNodeForId (destNodeId); if (dest == 0 || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getNumInputChannels()) || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) return false; return getConnectionBetween (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex) == 0; } bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, const int sourceChannelIndex, const uint32 destNodeId, const int destChannelIndex) { if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) return false; Connection* const c = new Connection(); c->sourceNodeId = sourceNodeId; c->sourceChannelIndex = sourceChannelIndex; c->destNodeId = destNodeId; c->destChannelIndex = destChannelIndex; connections.add (c); 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 == 0 || dest == 0 || (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; } 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_) {} ~ClearChannelOp() {} 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_) {} ~CopyChannelOp() {} 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_) {} ~AddChannelOp() {} 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_) {} ~ClearMidiBufferOp() {} 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_) {} ~CopyMidiBufferOp() {} 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_) {} ~AddMidiBufferOp() {} 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 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); } ~ProcessBufferOp() { } 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_) { 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); } } 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) throw() { return nodeID != freeNodeID && nodeID != zeroNodeID; } 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; 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->id && 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; } } 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; 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; } for (int j = 0; j < sourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); } } } jassert (bufIndex >= 0); audioChannelsToUse.add (bufIndex); if (inputChan < numOuts) markBufferAsContaining (bufIndex, node->id, inputChan); } for (int outputChan = numIns; outputChan < numOuts; ++outputChan) { const int bufIndex = getFreeBuffer (false); jassert (bufIndex != 0); audioChannelsToUse.add (bufIndex); markBufferAsContaining (bufIndex, node->id, 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->id && 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->id, AudioProcessorGraph::midiChannelIndex); 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 { return 0; } int getBufferContaining (const uint32 nodeId, const int outputChannel) const { 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->id, AudioProcessorGraph::midiChannelIndex) != 0) return true; } else { for (int i = 0; i < node->getProcessor()->getNumInputChannels(); ++i) if (i != inputChannelOfIndexToIgnore && graph.getConnectionBetween (nodeId, outputChanIndex, node->id, i) != 0) 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); }; } 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; int i; for (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 (isAnInputTo (node->id, ((Node*) orderedNodes.getUnchecked (j))->id, nodes.size() + 1)) 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 = 0; currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); currentMidiInputBuffer = 0; 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 = 0; currentAudioOutputBuffer.setSize (1, 1); currentMidiInputBuffer = 0; 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 (0) { } 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 != 0) d.numInputChannels = graph->getNumInputChannels(); d.numOutputChannels = getNumOutputChannels(); if (type == audioInputNode && graph != 0) d.numOutputChannels = graph->getNumOutputChannels(); } void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) { jassert (graph != 0); } void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { jassert (graph != 0); 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 0; } 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 != 0) { 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 (0), sampleRate (0), blockSize (0), isPrepared (false), numInputChans (0), numOutputChans (0), tempBuffer (1, 1) { } AudioProcessorPlayer::~AudioProcessorPlayer() { setProcessor (0); } void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) { if (processor != processorToPlay) { if (processorToPlay != 0 && sampleRate > 0 && blockSize > 0) { processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); processorToPlay->prepareToPlay (sampleRate, blockSize); } AudioProcessor* oldOne; { const ScopedLock sl (lock); oldOne = isPrepared ? processor : 0; processor = processorToPlay; isPrepared = true; } if (oldOne != 0) 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 != 0) { 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 != 0) { if (isPrepared) processor->releaseResources(); AudioProcessor* const oldProcessor = processor; setProcessor (0); setProcessor (oldProcessor); } } void AudioProcessorPlayer::audioDeviceStopped() { const ScopedLock sl (lock); if (processor != 0 && 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_ != 0); 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) != 0; } void SamplerVoice::startNote (const int midiNoteNumber, const float velocity, SynthesiserSound* s, const int /*currentPitchWheelPosition*/) { const SamplerSound* const sound = dynamic_cast (s); jassert (sound != 0); // this object can only play SamplerSounds! if (sound != 0) { 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 != 0) { const float* const inL = playingSound->data->getSampleData (0, 0); const float* const inR = playingSound->data->getNumChannels() > 1 ? playingSound->data->getSampleData (1, 0) : 0; float* outL = outputBuffer.getSampleData (0, startSample); float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : 0; 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 != 0) ? (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 != 0) { *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), currentlyPlayingSound (0) { } SynthesiserVoice::~SynthesiserVoice() { } bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const { return currentlyPlayingSound != 0 && currentlyPlayingSound->appliesToChannel (midiChannel); } void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) { currentSampleRate = newRate; } void SynthesiserVoice::clearCurrentNote() { currentlyPlayingNote = -1; currentlyPlayingSound = 0; } 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) { if (m.isNoteOn()) { const int channel = m.getChannel(); noteOn (channel, 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()); } } startSample += numThisTime; numSamples -= numThisTime; } } 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)) { 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 != 0 && sound != 0) { if (voice->currentlyPlayingSound != 0) voice->stopNote (false); voice->startNote (midiNoteNumber, velocity, sound, lastPitchWheelValues [midiChannel - 1]); voice->currentlyPlayingNote = midiNoteNumber; voice->noteOnTime = ++lastNoteOnCounter; voice->currentlyPlayingSound = sound; } } 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 != 0 && sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) { 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::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); } } 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) { 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); } } 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 = 0; for (int i = voices.size(); --i >= 0;) { SynthesiserVoice* const voice = voices.getUnchecked (i); if (voice->canPlaySound (soundToPlay) && (oldest == 0 || oldest->noteOnTime > voice->noteOnTime)) oldest = voice; } jassert (oldest != 0); return oldest; } return 0; } 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_) throw() : 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 != 0); callback.owner = this; } ActionBroadcaster::~ActionBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != 0); } void ActionBroadcaster::addActionListener (ActionListener* const listener) { const ScopedLock sl (actionListenerLock); if (listener != 0) 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 throw() { 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() throw() { 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 throw() { 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() throw() { // are you trying to create this object before or after juce has been intialised?? jassert (MessageManager::instance != 0); callback.owner = this; } ChangeBroadcaster::~ChangeBroadcaster() { // all event-based objects must be deleted BEFORE juce is shut down! jassert (MessageManager::instance != 0); } 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 (0) { } void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate() { jassert (owner != 0); 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 = 0; 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 != 0) socket->close(); if (pipe != 0) { pipe->cancelPendingReads(); pipe->close(); } stopThread (4000); { const ScopedLock sl (pipeAndSocketLock); socket = 0; pipe = 0; } connectionLostInt(); } bool InterprocessConnection::isConnected() const { const ScopedLock sl (pipeAndSocketLock); return ((socket != 0 && socket->isConnected()) || (pipe != 0 && pipe->isOpen())) && isThreadRunning(); } const String InterprocessConnection::getConnectedHostName() const { if (pipe != 0) { return "localhost"; } else if (socket != 0) { 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()); size_t bytesWritten = 0; const ScopedLock sl (pipeAndSocketLock); if (socket != 0) { bytesWritten = socket->write (messageData.getData(), (int) messageData.getSize()); } else if (pipe != 0) { bytesWritten = pipe->write (messageData.getData(), (int) messageData.getSize()); } if (bytesWritten < 0) { // error.. return false; } return (bytesWritten == 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 != 0) ? 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 != 0) ? 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 = 0; } connectionLostInt(); return false; } return true; } void InterprocessConnection::run() { while (! threadShouldExit()) { if (socket != 0) { const int ready = socket->waitUntilReady (true, 0); if (ready < 0) { { const ScopedLock sl (pipeAndSocketLock); socket = 0; } connectionLostInt(); break; } else if (ready > 0) { if (! readNextMessageInt()) break; } else { Thread::sleep (2); } } else if (pipe != 0) { if (! pipe->isOpen()) { { const ScopedLock sl (pipeAndSocketLock); pipe = 0; } 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 = 0; return false; } void InterprocessConnectionServer::stop() { signalThreadShouldExit(); if (socket != 0) socket->close(); stopThread (4000); socket = 0; } void InterprocessConnectionServer::run() { while ((! threadShouldExit()) && socket != 0) { ScopedPointer clientSocket (socket->waitForNextConnection()); if (clientSocket != 0) { InterprocessConnection* newConnection = createConnectionObject(); if (newConnection != 0) 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() throw() : intParameter1 (0), intParameter2 (0), intParameter3 (0), pointerParameter (0), messageRecipient (0) { } Message::Message (const int intParameter1_, const int intParameter2_, const int intParameter3_, void* const pointerParameter_) throw() : intParameter1 (intParameter1_), intParameter2 (intParameter2_), intParameter3 (intParameter3_), pointerParameter (pointerParameter_), messageRecipient (0) { } Message::~Message() { } END_JUCE_NAMESPACE /*** End of inlined file: juce_Message.cpp ***/ /*** Start of inlined file: juce_MessageListener.cpp ***/ BEGIN_JUCE_NAMESPACE MessageListener::MessageListener() throw() { // are you trying to create a messagelistener before or after juce has been intialised?? jassert (MessageManager::instance != 0); if (MessageManager::instance != 0) MessageManager::instance->messageListeners.add (this); } MessageListener::~MessageListener() { if (MessageManager::instance != 0) MessageManager::instance->messageListeners.removeValue (this); } void MessageListener::postMessage (Message* const message) const throw() { message->messageRecipient = const_cast (this); if (MessageManager::instance == 0) MessageManager::getInstance(); MessageManager::instance->postMessageToQueue (message); } bool MessageListener::isValidMessageListener() const throw() { return (MessageManager::instance != 0) && 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 // platform-specific functions.. bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); bool juce_postMessageToSystemQueue (Message* message); MessageManager* MessageManager::instance = 0; static const int quitMessageId = 0xfffff321; MessageManager::MessageManager() throw() : quitMessagePosted (false), quitMessageReceived (false), threadWithLock (0) { messageThreadId = Thread::getCurrentThreadId(); if (JUCEApplication::isStandaloneApp()) Thread::setCurrentThreadName ("Juce Message Thread"); } MessageManager::~MessageManager() throw() { broadcaster = 0; doPlatformSpecificShutdown(); // If you hit this assertion, then you've probably leaked some kind of MessageListener object.. jassert (messageListeners.size() == 0); jassert (instance == this); instance = 0; // do this last in case this instance is still needed by doPlatformSpecificShutdown() } MessageManager* MessageManager::getInstance() throw() { if (instance == 0) { instance = new MessageManager(); doPlatformSpecificInitialisation(); } return instance; } void MessageManager::postMessageToQueue (Message* const message) { if (quitMessagePosted || ! juce_postMessageToSystemQueue (message)) Message::Ptr deleter (message); // (this will delete messages that were just created with a 0 ref count) } CallbackMessage::CallbackMessage() throw() {} CallbackMessage::~CallbackMessage() {} void CallbackMessage::post() { if (MessageManager::instance != 0) MessageManager::instance->postMessageToQueue (this); } // not for public use.. void MessageManager::deliverMessage (Message* const message) { JUCE_TRY { MessageListener* const recipient = message->messageRecipient; if (recipient == 0) { CallbackMessage* const callbackMessage = dynamic_cast (message); if (callbackMessage != 0) { callbackMessage->messageCallback(); } else if (message->intParameter1 == quitMessageId) { quitMessageReceived = true; } } else if (messageListeners.contains (recipient)) { recipient->handleMessage (*message); } } JUCE_CATCH_EXCEPTION } #if ! (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 (quitMessageId, 0, 0, 0)); 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 (! juce_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 != 0) broadcaster->sendActionMessage (value); } void MessageManager::registerBroadcastListener (ActionListener* const listener) { if (broadcaster == 0) broadcaster = new ActionBroadcaster(); broadcaster->addActionListener (listener); } void MessageManager::deregisterBroadcastListener (ActionListener* const listener) { if (broadcaster != 0) broadcaster->removeActionListener (listener); } bool MessageManager::isThisTheMessageThread() const throw() { 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 throw() { 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, 0); } MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) : locked (false) { init (0, jobToCheckForExitSignal); } void MessageManagerLock::init (Thread* const threadToCheck, ThreadPoolJob* const job) { if (MessageManager::instance != 0) { if (MessageManager::instance->currentThreadHasLockedMessageManager()) { locked = true; // either we're on the message thread, or this is a re-entrant call. } else { if (threadToCheck == 0 && job == 0) { MessageManager::instance->lockingLock.enter(); } else { while (! MessageManager::instance->lockingLock.tryEnter()) { if ((threadToCheck != 0 && threadToCheck->threadShouldExit()) || (job != 0 && job->shouldExit())) return; Thread::sleep (1); } } blockingMessage = new BlockingMessage(); blockingMessage->post(); while (! blockingMessage->lockedEvent.wait (20)) { if ((threadToCheck != 0 && threadToCheck->threadShouldExit()) || (job != 0 && job->shouldExit())) { blockingMessage->releaseEvent.signal(); blockingMessage = 0; MessageManager::instance->lockingLock.exit(); return; } } jassert (MessageManager::instance->threadWithLock == 0); MessageManager::instance->threadWithLock = Thread::getCurrentThreadId(); locked = true; } } } MessageManagerLock::~MessageManagerLock() throw() { if (blockingMessage != 0) { jassert (MessageManager::instance == 0 || MessageManager::instance->currentThreadHasLockedMessageManager()); blockingMessage->releaseEvent.signal(); blockingMessage = 0; if (MessageManager::instance != 0) { 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_) { } ~MultiTimerCallback() { } void timerCallback() { owner.timerCallback (timerId); } const int timerId; private: MultiTimer& owner; }; MultiTimer::MultiTimer() throw() { } MultiTimer::MultiTimer (const MultiTimer&) throw() { } MultiTimer::~MultiTimer() { const ScopedLock sl (timerListLock); timers.clear(); } void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds) throw() { const ScopedLock 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) throw() { const ScopedLock 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 throw() { const ScopedLock 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 throw() { const ScopedLock 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: InternalTimerThread() : Thread ("Juce Timer"), firstTimer (0), callbackNeeded (0) { triggerAsyncUpdate(); } ~InternalTimerThread() throw() { stopThread (4000); jassert (instance == this || instance == 0); if (instance == this) instance = 0; } void run() { uint32 lastTime = Time::getMillisecondCounter(); Message::Ptr message (new Message()); while (! threadShouldExit()) { const uint32 now = Time::getMillisecondCounter(); if (now <= lastTime) { wait (2); continue; } const int elapsed = now - lastTime; 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 (message); /* 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 ScopedLock sl (lock); while (firstTimer != 0 && firstTimer->countdownMs <= 0) { Timer* const t = firstTimer; t->countdownMs = t->periodMs; removeTimer (t); addTimer (t); const ScopedUnlock 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 != 0) InternalTimerThread::instance->callTimersSynchronously(); } static inline void add (Timer* const tim) throw() { if (instance == 0) instance = new InternalTimerThread(); const ScopedLock sl (instance->lock); instance->addTimer (tim); } static inline void remove (Timer* const tim) throw() { if (instance != 0) { const ScopedLock sl (instance->lock); instance->removeTimer (tim); } } static inline void resetCounter (Timer* const tim, const int newCounter) throw() { if (instance != 0) { tim->countdownMs = newCounter; tim->periodMs = newCounter; if ((tim->next != 0 && tim->next->countdownMs < tim->countdownMs) || (tim->previous != 0 && tim->previous->countdownMs > tim->countdownMs)) { const ScopedLock sl (instance->lock); instance->removeTimer (tim); instance->addTimer (tim); } } } private: friend class Timer; static InternalTimerThread* instance; static CriticalSection lock; Timer* volatile firstTimer; Atomic callbackNeeded; void addTimer (Timer* const t) throw() { #if JUCE_DEBUG Timer* tt = firstTimer; while (tt != 0) { // 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 == 0 && t->next == 0); #endif Timer* i = firstTimer; if (i == 0 || i->countdownMs > t->countdownMs) { t->next = firstTimer; firstTimer = t; } else { while (i->next != 0 && i->next->countdownMs <= t->countdownMs) i = i->next; jassert (i != 0); t->next = i->next; t->previous = i; i->next = t; } if (t->next != 0) t->next->previous = t; jassert ((t->next == 0 || t->next->countdownMs >= t->countdownMs) && (t->previous == 0 || t->previous->countdownMs <= t->countdownMs)); notify(); } void removeTimer (Timer* const t) throw() { #if JUCE_DEBUG Timer* tt = firstTimer; bool found = false; while (tt != 0) { 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 != 0) { jassert (firstTimer != t); t->previous->next = t->next; } else { jassert (firstTimer == t); firstTimer = t->next; } if (t->next != 0) t->next->previous = t->previous; t->next = 0; t->previous = 0; } int getTimeUntilFirstTimer (const int numMillisecsElapsed) const { const ScopedLock sl (lock); for (Timer* t = firstTimer; t != 0; t = t->next) t->countdownMs -= numMillisecsElapsed; return firstTimer != 0 ? firstTimer->countdownMs : 1000; } void handleAsyncUpdate() { startThread (7); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalTimerThread); }; InternalTimerThread* InternalTimerThread::instance = 0; CriticalSection InternalTimerThread::lock; void juce_callAnyTimersSynchronously() { InternalTimerThread::callAnyTimersSynchronously(); } #if JUCE_DEBUG static SortedSet activeTimers; #endif Timer::Timer() throw() : countdownMs (0), periodMs (0), previous (0), next (0) { #if JUCE_DEBUG activeTimers.add (this); #endif } Timer::Timer (const Timer&) throw() : countdownMs (0), periodMs (0), previous (0), next (0) { #if JUCE_DEBUG activeTimers.add (this); #endif } Timer::~Timer() { stopTimer(); #if JUCE_DEBUG activeTimers.removeValue (this); #endif } void Timer::startTimer (const int interval) throw() { const ScopedLock 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() throw() { const ScopedLock 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 = 0; 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 != 0) { 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 != 0) { MouseListenerList* const list = p->mouseListeners; if (list != 0 && 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 != 0) { 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 != 0) { MouseListenerList* const list = p->mouseListeners; if (list != 0 && 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 throw() { 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: static void* runModalLoopCallback (void* userData) { return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); } static const Identifier getColourPropertyId (const int colourId) { String s; s.preallocateStorage (18); 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 == 0) 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 == 0) 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 == 0) 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 == 0) 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 != 0); 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 != 0) { if (source == target) return p; if (source->isParentOf (target)) return convertFromDistantParentSpace (source, *target, p); if (source->isOnDesktop()) { p = source->getPeer()->localToGlobal (p); source = 0; } else { p = convertToParentSpace (*source, p); source = source->getParentComponent(); } } jassert (source == 0); if (target == 0) 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 != 0) 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() != 0 ? comp.getParentComponent()->getLocalBounds() : Desktop::getInstance().getMainMonitorArea(); } }; Component::Component() : parentComponent (0), lookAndFeel (0), effect (0), componentFlags (0), componentTransparency (0) { } Component::Component (const String& name) : componentName (name), parentComponent (0), lookAndFeel (0), effect (0), 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 != 0) 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 != 0); if (peer != 0) 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 != 0) parentComponent->grabKeyboardFocus(); else giveAwayFocus (true); } } if (safePointer != 0) { sendVisibilityChangeMessage(); if (safePointer != 0 && flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); jassert (peer != 0); if (peer != 0) { 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 != 0) { return parentComponent->isShowing(); } else { const ComponentPeer* const peer = getPeer(); return peer != 0 && ! peer->isMinimised(); } } return false; } void* Component::getWindowHandle() const { const ComponentPeer* const peer = getPeer(); if (peer != 0) return peer->getNativeHandle(); return 0; } 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 != 0) 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 = 0; Rectangle oldNonFullScreenBounds; if (peer != 0) { wasFullscreen = peer->isFullScreen(); wasMinimised = peer->isMinimised(); currentConstainer = peer->getConstrainer(); oldNonFullScreenBounds = peer->getNonFullScreenBounds(); removeFromDesktop(); setTopLeftPosition (topLeft.getX(), topLeft.getY()); } if (parentComponent != 0) parentComponent->removeChildComponent (this); if (safePointer != 0) { 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 != 0); delete peer; Desktop::getInstance().removeDesktopComponent (this); } } bool Component::isOnDesktop() const throw() { 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 != 0) { // to make it recreate the heavyweight window addToDesktop (peer->getStyleFlags()); } } repaint(); } } bool Component::isOpaque() const throw() { 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 != 0); 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 != 0) { peer->toFront (setAsForeground); if (setAsForeground && ! hasKeyboardFocus (true)) grabKeyboardFocus(); } } else if (parentComponent != 0) { 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 != 0 && other != this) { // the two components must belong to the same parent.. jassert (parentComponent == other->parentComponent); if (parentComponent != 0) { 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 != 0 && them != 0); if (us != 0 && them != 0) us->toBehind (them); } } } } void Component::toBack() { if (isOnDesktop()) { jassertfalse; //xxx need to add this to native window } else if (parentComponent != 0) { 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) { flags.alwaysOnTopFlag = shouldStayOnTop; if (isOnDesktop()) { ComponentPeer* const peer = getPeer(); jassert (peer != 0); if (peer != 0) { 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) toFront (false); internalHierarchyChanged(); } } bool Component::isAlwaysOnTop() const throw() { return flags.alwaysOnTopFlag; } int Component::proportionOfWidth (const float proportion) const throw() { return roundToInt (proportion * bounds.getWidth()); } int Component::proportionOfHeight (const float proportion) const throw() { return roundToInt (proportion * bounds.getHeight()); } int Component::getParentWidth() const throw() { return (parentComponent != 0) ? parentComponent->getWidth() : getParentMonitorArea().getWidth(); } int Component::getParentHeight() const throw() { return (parentComponent != 0) ? parentComponent->getHeight() : getParentMonitorArea().getHeight(); } int Component::getScreenX() const { return getScreenPosition().getX(); } int Component::getScreenY() const { return getScreenPosition().getY(); } const Point Component::getScreenPosition() const { return localPointToGlobal (Point()); } const Rectangle Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } const Point Component::getLocalPoint (const Component* source, const Point& point) const { return ComponentHelpers::convertCoordinate (this, source, point); } const Rectangle Component::getLocalArea (const Component* source, const Rectangle& area) const { return ComponentHelpers::convertCoordinate (this, source, area); } const Point Component::localPointToGlobal (const Point& point) const { return ComponentHelpers::convertCoordinate (0, this, point); } const Rectangle Component::localAreaToGlobal (const Rectangle& area) const { return ComponentHelpers::convertCoordinate (0, 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 (0, screenPosition); } const Point Component::relativePositionToOtherComponent (const Component* const targetComponent, const Point& positionRelativeToThis) const { return targetComponent == 0 ? 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(); } if (flags.hasHeavyweightPeerFlag) { ComponentPeer* const peer = getPeer(); if (peer != 0) { 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 != 0) 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::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 throw() { return affineTransform != 0; } 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 != 0) { repaint(); affineTransform = 0; repaint(); sendMovedResizedMessages (false, false); } } else if (affineTransform == 0) { repaint(); affineTransform = new AffineTransform (newTransform); repaint(); sendMovedResizedMessages (false, false); } else if (*affineTransform != newTransform) { repaint(); *affineTransform = newTransform; repaint(); sendMovedResizedMessages (false, false); } } const AffineTransform Component::getTransform() const { return affineTransform != 0 ? *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) throw() { flags.ignoresMouseClicksFlag = ! allowClicks; flags.allowChildMouseClicksFlag = allowClicksOnChildComponents; } void Component::getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, bool& allowsClicksOnChildComponents) const throw() { allowsClicksOnThisComponent = ! flags.ignoresMouseClicksFlag; allowsClicksOnChildComponents = flags.allowChildMouseClicksFlag; } bool Component::contains (const Point& point) { if (ComponentHelpers::hitTest (*this, point)) { if (parentComponent != 0) { return parentComponent->contains (ComponentHelpers::convertToParentSpace (*this, point)); } else if (flags.hasHeavyweightPeerFlag) { const ComponentPeer* const peer = getPeer(); if (peer != 0) 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 != 0) return child; } return this; } return 0; } 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 != 0 && child->parentComponent != this) { if (child->parentComponent != 0) 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 != 0) { 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 != 0) { sendParentEvents = sendParentEvents && child->isShowing(); if (sendParentEvents) { sendFakeMouseMove(); child->repaintParent(); } childComponentList.remove (index); child->parentComponent = 0; // (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 == 0) 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 throw() { return childComponentList.size(); } Component* Component::getChildComponent (const int index) const throw() { return childComponentList [index]; } int Component::getIndexOfChildComponent (const Component* const child) const throw() { return childComponentList.indexOf (const_cast (child)); } Component* Component::getTopLevelComponent() const throw() { const Component* comp = this; while (comp->parentComponent != 0) comp = comp->parentComponent; return const_cast (comp); } bool Component::isParentOf (const Component* possibleChild) const throw() { while (possibleChild != 0) { 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()); } } 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(); } void Component::enterModalState (const bool shouldTakeKeyboardFocus, ModalComponentManager::Callback* const callback) { // 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::getInstance()->startModal (this, callback); flags.currentlyModalFlag = true; setVisible (true); if (shouldTakeKeyboardFocus) grabKeyboardFocus(); } } void Component::exitModalState (const int returnValue) { if (isCurrentlyModal()) { 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() != 0) // (get() required for VS2003 bug) target->exitModalState (result); } private: WeakReference target; int result; }; (new ExitModalStateMessage (this, returnValue))->post(); } } } bool Component::isCurrentlyModal() const throw() { return flags.currentlyModalFlag && getCurrentlyModalComponent() == this; } bool Component::isCurrentlyBlockedByAnotherModalComponent() const { Component* const mc = getCurrentlyModalComponent(); return mc != 0 && mc != this && (! mc->isParentOf (this)) && ! mc->canModalEventBeSentToComponent (this); } int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() throw() { return ModalComponentManager::getInstance()->getNumModalComponents(); } Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) throw() { return ModalComponentManager::getInstance()->getModalComponent (index); } void Component::setBroughtToFrontOnMouseClick (const bool shouldBeBroughtToFront) throw() { flags.bringToFrontOnClickFlag = shouldBeBroughtToFront; } bool Component::isBroughtToFrontOnMouseClick() const throw() { 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 { sendFakeMouseMove(); } void Component::setRepaintsOnMouseActivity (const bool shouldRepaint) throw() { 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 != 0) 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 != 0) { if (parentComponent->flags.visibleFlag) { if (affineTransform == 0) { 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 != 0) 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.withAlpha (getAlpha())); 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 != 0) { 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 == 0) { 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 != 0) { 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) throw() { flags.dontClipGraphicsFlag = shouldPaintWithoutClipping; } const 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 throw() { const Component* c = this; do { if (c->lookAndFeel != 0) return *(c->lookAndFeel); c = c->parentComponent; } while (c != 0); 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 != 0) { for (int i = childComponentList.size(); --i >= 0;) { childComponentList.getUnchecked (i)->sendLookAndFeelChange(); if (safePointer == 0) return; i = jmin (i, childComponentList.size()); } } } const Colour Component::findColour (const int colourId, const bool inheritFromParent) const { var* const v = properties.getVarPointer (ComponentHelpers::getColourPropertyId (colourId)); if (v != 0) return Colour ((int) *v); if (inheritFromParent && parentComponent != 0) 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 0; } Component::Positioner::Positioner (Component& component_) throw() : component (component_) { } Component::Positioner* Component::getPositioner() const throw() { return positioner; } void Component::setPositioner (Positioner* newPositioner) { // You can only assign a positioner to the component that it was created for! jassert (newPositioner == 0 || this == &(newPositioner->getComponent())); positioner = newPositioner; } const Rectangle Component::getLocalBounds() const throw() { return Rectangle (getWidth(), getHeight()); } const Rectangle Component::getBoundsInParent() const throw() { return affineTransform == 0 ? 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, 0); 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 != 0) 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 != 0) 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() != 0) // (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 == 0) 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 != 0) 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 != 0) { 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 (0, 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 (0, 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 != 0 && cm->getTopLevelComponent() != getTopLevelComponent()) ModalComponentManager::getInstance()->bringModalComponentsToFront(); } 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 != 0) internalChildFocusChange (cause, safePointer); } void Component::focusLost (FocusChangeType) { // base class does nothing } void Component::internalFocusLoss (const FocusChangeType cause) { WeakReference safePointer (this); focusLost (focusChangedDirectly); if (safePointer != 0) 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 == 0) return; } if (parentComponent != 0) parentComponent->internalChildFocusChange (cause, WeakReference (parentComponent)); } bool Component::isEnabled() const throw() { return (! flags.isDisabledFlag) && (parentComponent == 0 || 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 == 0 || parentComponent->isEnabled()) sendEnablementChangeMessage(); } } void Component::sendEnablementChangeMessage() { WeakReference safePointer (this); enablementChanged(); if (safePointer == 0) return; for (int i = getNumChildComponents(); --i >= 0;) { Component* const c = getChildComponent (i); if (c != 0) { c->sendEnablementChangeMessage(); if (safePointer == 0) return; } } } void Component::enablementChanged() { } void Component::setWantsKeyboardFocus (const bool wantsFocus) throw() { flags.wantsFocusFlag = wantsFocus; } void Component::setMouseClickGrabsKeyboardFocus (const bool shouldGrabFocus) { flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus; } bool Component::getMouseClickGrabsKeyboardFocus() const throw() { return ! flags.dontFocusOnMouseClickFlag; } bool Component::getWantsKeyboardFocus() const throw() { return flags.wantsFocusFlag && ! flags.isDisabledFlag; } void Component::setFocusContainer (const bool shouldBeFocusContainer) throw() { flags.isFocusContainerFlag = shouldBeFocusContainer; } bool Component::isFocusContainer() const throw() { 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 == 0) 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 != 0) { 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 != 0) 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 == 0)) { 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 != 0) { Component* const defaultComp = traverser->getDefaultComponent (this); traverser = 0; if (defaultComp != 0) { defaultComp->grabFocusInternal (cause, false); return; } } if (canTryParent && parentComponent != 0) { // 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 != 0) { ScopedPointer traverser (createFocusTraverser()); if (traverser != 0) { Component* const nextComp = moveToNext ? traverser->getNextComponent (this) : traverser->getPreviousComponent (this); traverser = 0; if (nextComp != 0) { if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) { WeakReference nextCompPointer (nextComp); internalModalInputAttempt(); if (nextCompPointer == 0 || 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() throw() { return currentlyFocusedComponent; } void Component::giveAwayFocus (const bool sendFocusLossEvent) { Component* const componentLosingFocus = currentlyFocusedComponent; currentlyFocusedComponent = 0; if (sendFocusLossEvent && componentLosingFocus != 0) 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 throw() { return flags.mouseDownFlag; } bool Component::isMouseOverOrDragging() const throw() { return flags.mouseOverFlag || flags.mouseDownFlag; } bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() throw() { return ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(); } const Point Component::getMouseXYRelative() const { return getLocalPoint (0, Desktop::getMousePosition()); } const Rectangle Component::getParentMonitorArea() const { return Desktop::getInstance().getMonitorAreaContaining (getScreenBounds().getCentre()); } void Component::addKeyListener (KeyListener* const newListener) { if (keyListeners == 0) keyListeners = new Array (); keyListeners->addIfNotAlreadyThere (newListener); } void Component::removeKeyListener (KeyListener* const listenerToRemove) { if (keyListeners != 0) 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 != 0) parentComponent->modifierKeysChanged (modifiers); } void Component::internalModifierKeysChanged() { sendFakeMouseMove(); modifierKeysChanged (ModifierKeys::getCurrentModifiers()); } ComponentPeer* Component::getPeer() const { if (flags.hasHeavyweightPeerFlag) return ComponentPeer::getPeerFor (this); else if (parentComponent == 0) return 0; return parentComponent->getPeer(); } Component::BailOutChecker::BailOutChecker (Component* const component) : safePointer (component) { jassert (component != 0); } bool Component::BailOutChecker::shouldBailOut() const throw() { return safePointer == 0; } 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 (0), allowedOrientations (allOrientations) { createMouseInputSources(); refreshMonitorSizes(); } Desktop::~Desktop() { jassert (instance == this); instance = 0; // 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 == 0) instance = new Desktop(); return *instance; } Desktop* Desktop::instance = 0; extern void juce_updateMultiMonitorInfo (Array >& monitorCoords, const bool clipToWorkArea); void Desktop::refreshMonitorSizes() { Array > oldClipped, oldUnclipped; oldClipped.swapWithArray (monitorCoordsClipped); oldUnclipped.swapWithArray (monitorCoordsUnclipped); juce_updateMultiMonitorInfo (monitorCoordsClipped, true); juce_updateMultiMonitorInfo (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 != 0) p->handleScreenSizeChange(); } } } int Desktop::getNumDisplayMonitors() const throw() { return monitorCoordsClipped.size(); } const Rectangle Desktop::getDisplayMonitorCoordinates (const int index, const bool clippedToWorkArea) const throw() { return clippedToWorkArea ? monitorCoordsClipped [index] : monitorCoordsUnclipped [index]; } const RectangleList Desktop::getAllMonitorDisplayAreas (const bool clippedToWorkArea) const throw() { RectangleList rl; for (int i = 0; i < getNumDisplayMonitors(); ++i) rl.addWithoutMerging (getDisplayMonitorCoordinates (i, clippedToWorkArea)); return rl; } const Rectangle Desktop::getMainMonitorArea (const bool clippedToWorkArea) const throw() { 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 throw() { return desktopComponents.size(); } Component* Desktop::getComponent (const int index) const throw() { 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 (0, screenPosition)); if (c->contains (relative)) return c->getComponentAt (relative); } } return 0; } void Desktop::addDesktopComponent (Component* const c) { jassert (c != 0); 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() throw() { ++mouseClickCounter; } int Desktop::getNumDraggingMouseSources() const throw() { int num = 0; for (int i = mouseSources.size(); --i >= 0;) if (mouseSources.getUnchecked(i)->isDragging()) ++num; return num; } MouseInputSource* Desktop::getDraggingMouseSource (int index) const throw() { 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 0; } 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 == 0) dragRepeater = new MouseDragAutoRepeater(); if (dragRepeater->getTimerInterval() != interval) dragRepeater->startTimer (interval); } else { dragRepeater = 0; } } 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 != 0) { Component::BailOutChecker checker (target); const Point pos (target->getLocalPoint (0, 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(); } extern void juce_setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars); 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 == 0 || ComponentPeer::getPeerFor (kioskModeComponent) != 0); if (kioskModeComponent != 0) { juce_setKioskComponent (kioskModeComponent, false, allowMenusAndBars); kioskModeComponent->setBounds (kioskComponentOriginalBounds); } kioskModeComponent = componentToUse; if (kioskModeComponent != 0) { // Only components that are already on the desktop can be put into kiosk mode! jassert (ComponentPeer::getPeerFor (kioskModeComponent) != 0); kioskComponentOriginalBounds = kioskModeComponent->getBounds(); juce_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 throw() { // 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 ComponentListener { public: ModalItem (Component* const comp, Callback* const callback) : component (comp), returnValue (0), isActive (true), isDeleted (false) { if (callback != 0) callbacks.add (callback); jassert (comp != 0); component->addComponentListener (this); } ~ModalItem() { if (! isDeleted) component->removeComponentListener (this); } void componentBeingDeleted (Component&) { isDeleted = true; cancel(); } void componentVisibilityChanged (Component&) { if (! component->isShowing()) cancel(); } void componentParentHierarchyChanged (Component&) { if (! component->isShowing()) cancel(); } void cancel() { if (isActive) { isActive = false; ModalComponentManager::getInstance()->triggerAsyncUpdate(); } } Component* component; OwnedArray callbacks; int returnValue; bool isActive, isDeleted; private: JUCE_DECLARE_NON_COPYABLE (ModalItem); }; ModalComponentManager::ModalComponentManager() { } ModalComponentManager::~ModalComponentManager() { clearSingletonInstance(); } juce_ImplementSingleton_SingleThreaded (ModalComponentManager); void ModalComponentManager::startModal (Component* component, Callback* callback) { if (component != 0) stack.add (new ModalItem (component, callback)); } void ModalComponentManager::attachCallback (Component* component, Callback* callback) { if (callback != 0) { 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 0; } 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) { for (int j = item->callbacks.size(); --j >= 0;) item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); stack.remove (i); } } } void ModalComponentManager::bringModalComponentsToFront() { ComponentPeer* lastOne = 0; for (int i = 0; i < getNumModalComponents(); ++i) { Component* const c = getModalComponent (i); if (c == 0) break; ComponentPeer* peer = c->getPeer(); if (peer != 0 && peer != lastOne) { if (lastOne == 0) { peer->toFront (true); peer->grabFocus(); } else peer->toBehind (lastOne); lastOne = peer; } } } class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback { public: ReturnValueRetriever (int& value_, bool& finished_) : value (value_), finished (finished_) {} ~ReturnValueRetriever() {} 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 == 0) 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 != 0) prevFocused->grabKeyboardFocus(); return returnValue; } 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); buttonStateChanged(); } 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() { 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 (0), 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 != 0) commandManagerToUse->removeListener (this); repeatTimer = 0; 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 != 0 && 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 == 0) return; } if (lastToggleState) { turnOffOtherButtonsInGroup (sendChangeNotification); if (deletionWatcher == 0) return; } sendStateMessage(); } } void Button::setClickingTogglesState (const bool shouldToggle) throw() { 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 == 0 || ! clickTogglesState); } bool Button::getClickingTogglesState() const throw() { 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 != 0 && 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 != 0 && b->getRadioGroupId() == radioGroupId) { b->setToggleState (false, sendChangeNotification); if (deletionWatcher == 0) 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 throw() { return buttonState == buttonDown; } bool Button::isOver() const throw() { return buttonState != buttonNormal; } void Button::buttonStateChanged() { } uint32 Button::getMillisecondsSinceButtonDown() const throw() { const uint32 now = Time::getApproximateMillisecondCounter(); return now > buttonPressTime ? now - buttonPressTime : 0; } void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) throw() { 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 != 0 && 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) ? 0 : getTopLevelComponent(); if (newKeySource != keySource.get()) { if (keySource != 0) keySource->removeKeyListener (this); keySource = newKeySource; if (keySource != 0) keySource->addKeyListener (this); } } void Button::setCommandToTrigger (ApplicationCommandManager* const commandManagerToUse_, const int commandID_, const bool generateTooltip_) { commandID = commandID_; generateTooltip = generateTooltip_; if (commandManagerToUse != commandManagerToUse_) { if (commandManagerToUse != 0) commandManagerToUse->removeListener (this); commandManagerToUse = commandManagerToUse_; if (commandManagerToUse != 0) 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 == 0 || ! clickTogglesState); } if (commandManagerToUse != 0) 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 != 0) { ApplicationCommandInfo info (0); ApplicationCommandTarget* const target = commandManagerToUse->getTargetForCommand (commandID, info); setEnabled (target != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0); if (target != 0) 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) throw() { 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 == 0) 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 (0), 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 != 0); // you really need to give it at least a normal image.. if (normal != 0) normalImage = normal->createCopy(); if (over != 0) overImage = over->createCopy(); if (down != 0) downImage = down->createCopy(); if (disabled != 0) disabledImage = disabled->createCopy(); if (normalOn != 0) normalImageOn = normalOn->createCopy(); if (overOn != 0) overImageOn = overOn->createCopy(); if (downOn != 0) downImageOn = downOn->createCopy(); if (disabledOn != 0) 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 throw() { return getToggleState() ? backgroundOn : backgroundOff; } void DrawableButton::setEdgeIndent (const int numPixelsIndent) { edgeIndent = numPixelsIndent; repaint(); resized(); } void DrawableButton::resized() { Button::resized(); if (currentImage != 0) { 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 = 0; float opacity = 1.0f; if (isEnabled()) { imageToDraw = getCurrentImage(); } else { imageToDraw = getToggleState() ? disabledImageOn : disabledImage; if (imageToDraw == 0) { opacity = 0.4f; imageToDraw = getNormalImage(); } } if (imageToDraw != currentImage) { removeChildComponent (currentImage); currentImage = imageToDraw; if (currentImage != 0) { addAndMakeVisible (currentImage); DrawableButton::resized(); } } if (currentImage != 0) 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 throw() { if (isDown()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } Drawable* DrawableButton::getNormalImage() const throw() { return (getToggleState() && normalImageOn != 0) ? normalImageOn : normalImage; } Drawable* DrawableButton::getOverImage() const throw() { Drawable* d = normalImage; if (getToggleState()) { if (overImageOn != 0) d = overImageOn; else if (normalImageOn != 0) d = normalImageOn; else if (overImage != 0) d = overImage; } else { if (overImage != 0) d = overImage; } return d; } Drawable* DrawableButton::getDownImage() const throw() { Drawable* d = normalImage; if (getToggleState()) { if (downImageOn != 0) d = downImageOn; else if (overImageOn != 0) d = overImageOn; else if (normalImageOn != 0) d = normalImageOn; else if (downImage != 0) d = downImage; else d = getOverImage(); } else { if (downImage != 0) 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) throw() { url = newURL; setTooltip (newURL.toString (false)); } const 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), normalImage (0), overImage (0), downImage (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(); } const Image ImageButton::getCurrentImage() const { if (isDown() || getToggleState()) return getDownImage(); if (isOver()) return getOverImage(); return getNormalImage(); } const Image ImageButton::getNormalImage() const { return normalImage; } const Image ImageButton::getOverImage() const { return overImage.isValid() ? overImage : normalImage; } const 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 bounds (shape.getBounds()); if (hasShadow) bounds.expand (4.0f, 4.0f); shape.applyTransform (AffineTransform::translation (-bounds.getX(), -bounds.getY())); setSize (1 + (int) (bounds.getWidth() + outlineWidth), 1 + (int) (bounds.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() != 0) { 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 (0) { jassert (normalImage_ != 0); } 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 != 0) { 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 != 0) d = toggledOnImage; if (d != currentImage) { removeChildComponent (currentImage); currentImage = d; if (d != 0) { 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 juce_wchar* const line_, const int lineLength_, const int numNewLineChars, const int lineStartInFile_) : line (line_, lineLength_), lineStartInFile (lineStartInFile_), lineLength (lineLength_), lineLengthWithoutNewLines (lineLength_ - numNewLineChars) { } ~CodeDocumentLine() { } static void createLines (Array & newLines, const String& text) { const juce_wchar* const t = text; int pos = 0; while (t [pos] != 0) { const int startOfLine = pos; int numNewLineChars = 0; while (t[pos] != 0) { if (t[pos] == '\r') { ++numNewLineChars; ++pos; if (t[pos] == '\n') { ++numNewLineChars; ++pos; } break; } if (t[pos] == '\n') { ++numNewLineChars; ++pos; break; } ++pos; } newLines.add (new CodeDocumentLine (t + startOfLine, pos - startOfLine, numNewLineChars, startOfLine)); } jassert (pos == text.length()); } bool endsWithLineBreak() const throw() { return lineLengthWithoutNewLines != lineLength; } void updateLength() throw() { lineLengthWithoutNewLines = lineLength = line.length(); while (lineLengthWithoutNewLines > 0 && (line [lineLengthWithoutNewLines - 1] == '\n' || line [lineLengthWithoutNewLines - 1] == '\r')) { --lineLengthWithoutNewLines; } } String line; int lineStartInFile, lineLength, lineLengthWithoutNewLines; }; CodeDocument::Iterator::Iterator (CodeDocument* const document_) : document (document_), currentLine (document_->lines[0]), line (0), position (0) { } CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) : document (other.document), currentLine (other.currentLine), line (other.line), position (other.position) { } CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) throw() { document = other.document; currentLine = other.currentLine; line = other.line; position = other.position; return *this; } CodeDocument::Iterator::~Iterator() throw() { } juce_wchar CodeDocument::Iterator::nextChar() { if (currentLine == 0) return 0; jassert (currentLine == document->lines.getUnchecked (line)); const juce_wchar result = currentLine->line [position - currentLine->lineStartInFile]; if (++position >= currentLine->lineStartInFile + currentLine->lineLength) { ++line; currentLine = document->lines [line]; } return result; } void CodeDocument::Iterator::skip() { if (currentLine != 0) { jassert (currentLine == document->lines.getUnchecked (line)); if (++position >= currentLine->lineStartInFile + currentLine->lineLength) { ++line; currentLine = document->lines [line]; } } } void CodeDocument::Iterator::skipToEndOfLine() { if (currentLine != 0) { jassert (currentLine == document->lines.getUnchecked (line)); ++line; currentLine = document->lines [line]; if (currentLine != 0) position = currentLine->lineStartInFile; else position = document->getNumCharacters(); } } juce_wchar CodeDocument::Iterator::peekNextChar() const { if (currentLine == 0) return 0; jassert (currentLine == document->lines.getUnchecked (line)); return const_cast (currentLine->line) [position - currentLine->lineStartInFile]; } void CodeDocument::Iterator::skipWhitespace() { while (CharacterFunctions::isWhitespace (peekNextChar())) skip(); } bool CodeDocument::Iterator::isEOF() const throw() { return currentLine == 0; } CodeDocument::Position::Position() throw() : owner (0), characterPos (0), line (0), indexInLine (0), positionMaintained (false) { } CodeDocument::Position::Position (const CodeDocument* const ownerDocument, const int line_, const int indexInLine_) throw() : 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_) throw() : owner (const_cast (ownerDocument)), positionMaintained (false) { setPosition (characterPos_); } CodeDocument::Position::Position (const Position& other) throw() : 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 throw() { 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 throw() { return ! operator== (other); } void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine) { jassert (owner != 0); 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 != 0); indexInLine = l->lineLengthWithoutNewLines; characterPos = l->lineStartInFile + indexInLine; } else { line = jmax (0, newLineNum); CodeDocumentLine* const l = owner->lines.getUnchecked (line); jassert (l != 0); 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 != 0); 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 != 0); 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 == 0 ? 0 : l->line [getIndexInLine()]; } const String CodeDocument::Position::getLineText() const { const CodeDocumentLine* const l = owner->lines [line]; return l == 0 ? String::empty : l->line; } void CodeDocument::Position::setPositionMaintained (const bool isMaintained) { if (isMaintained != positionMaintained) { positionMaintained = isMaintained; if (owner != 0) { 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 == 0) ? String::empty : line->line.substring (start.getIndexInLine(), end.getIndexInLine()); } String result; result.preallocateStorage (end.getPosition() - start.getPosition() + 4); String::Concatenator concatenator (result); 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(); concatenator.append (line->line.substring (index, len)); } else if (i == endLine) { len = end.getIndexInLine(); concatenator.append (line->line.substring (0, len)); } else { concatenator.append (line->line); } } return result; } int CodeDocument::getNumCharacters() const throw() { const CodeDocumentLine* const lastLine = lines.getLast(); return (lastLine == 0) ? 0 : lastLine->lineStartInFile + lastLine->lineLength; } const String CodeDocument::getLine (const int lineIndex) const throw() { const CodeDocumentLine* const line = lines [lineIndex]; return (line == 0) ? String::empty : line->line; } int CodeDocument::getMaximumLineLength() throw() { 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) { replaceAllContent (stream.readEntireStreamAsString()); 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_) throw() { 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() throw() { indexOfSavedState = currentActionIndex; } bool CodeDocument::hasChangedSinceSavePoint() const throw() { return currentActionIndex != indexOfSavedState; } namespace CodeDocumentHelpers { int getCharacterType (const juce_wchar character) throw() { return (CharacterFunctions::isLetterOrDigit (character) || character == '_') ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); } } const CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const throw() { 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 throw() { 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 != 0 && 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, 0, 0, lastLine->lineStartInFile + lastLine->lineLength)); } } void CodeDocument::addListener (CodeDocument::Listener* const listener) throw() { listeners.add (listener); } void CodeDocument::removeListener (CodeDocument::Listener* const listener) throw() { 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_) throw() : 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 != 0) { 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 != 0 ? 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_) throw() : 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 != 0); 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::CaretComponent : public Component, public Timer { public: CaretComponent (CodeEditorComponent& owner_) : owner (owner_) { setAlwaysOnTop (true); setInterceptsMouseClicks (false, false); } void paint (Graphics& g) { g.fillAll (findColour (CodeEditorComponent::caretColourId)); } void timerCallback() { setVisible (shouldBeShown() && ! isVisible()); } void updatePosition() { startTimer (400); setVisible (shouldBeShown()); setBounds (owner.getCharacterBounds (owner.getCaretPos()).withWidth (2)); } private: CodeEditorComponent& owner; bool shouldBeShown() const { return owner.hasKeyboardFocus (true); } JUCE_DECLARE_NON_COPYABLE (CaretComponent); }; class CodeEditorComponent::CodeEditorLine { public: CodeEditorLine() throw() : 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 == 0) { 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) throw() : text (text_), tokenType (type), width (-1.0f) { } bool operator!= (const SyntaxToken& other) const throw() { 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 throw() { jassert (index <= line.length()); int col = 0; for (int i = 0; i < index; ++i) { if (line[i] != '\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 = new CaretComponent (*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::codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) { clearCachedIterators (affectedTextStart.getLineNumber()); triggerAsyncUpdate(); caret->updatePosition(); 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(); caret->updatePosition(); 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::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(); } caret->updatePosition(); 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; caret->updatePosition(); updateCachedIterators (firstLineOnScreen); triggerAsyncUpdate(); } } void CodeEditorComponent::scrollToColumnInternal (double column) { const double newOffset = jlimit (0.0, document.getMaximumLineLength() + 3.0, column); if (xOffset != newOffset) { xOffset = newOffset; caret->updatePosition(); 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); } void CodeEditorComponent::copy() { newTransaction(); const String selection (document.getTextBetween (selectionStart, selectionEnd)); if (selection.isNotEmpty()) SystemClipboard::copyTextToClipboard (selection); } void CodeEditorComponent::copyThenCut() { copy(); cut(); newTransaction(); } void CodeEditorComponent::paste() { newTransaction(); const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); newTransaction(); } void CodeEditorComponent::cursorLeft (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakBefore (caretPos), selecting); else moveCaretTo (caretPos.movedBy (-1), selecting); } void CodeEditorComponent::cursorRight (const bool moveInWholeWordSteps, const bool selecting) { newTransaction(); if (moveInWholeWordSteps) moveCaretTo (document.findWordBreakAfter (caretPos), selecting); else moveCaretTo (caretPos.movedBy (1), selecting); } 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; } void CodeEditorComponent::cursorDown (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); } void CodeEditorComponent::cursorUp (const bool selecting) { newTransaction(); if (caretPos.getLineNumber() == 0) moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); else moveLineDelta (-1, selecting); } void CodeEditorComponent::pageDown (const bool selecting) { newTransaction(); scrollBy (jlimit (0, linesOnScreen, 1 + document.getNumLines() - firstLineOnScreen - linesOnScreen)); moveLineDelta (linesOnScreen, selecting); } void CodeEditorComponent::pageUp (const bool selecting) { newTransaction(); scrollBy (-linesOnScreen); moveLineDelta (-linesOnScreen, selecting); } void CodeEditorComponent::scrollUp() { newTransaction(); scrollBy (1); if (caretPos.getLineNumber() < firstLineOnScreen) moveLineDelta (1, false); } void CodeEditorComponent::scrollDown() { newTransaction(); scrollBy (-1); if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) moveLineDelta (-1, false); } void CodeEditorComponent::goToStartOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); } namespace CodeEditorHelpers { int findFirstNonWhitespaceChar (const String& line) throw() { const int len = line.length(); for (int i = 0; i < len; ++i) if (! CharacterFunctions::isWhitespace (line [i])) return i; return 0; } } void CodeEditorComponent::goToStartOfLine (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); } void CodeEditorComponent::goToEndOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), selecting); } void CodeEditorComponent::goToEndOfLine (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), std::numeric_limits::max()), selecting); } void CodeEditorComponent::backspace (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(); } void CodeEditorComponent::deleteForward (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(); } void CodeEditorComponent::selectAll() { newTransaction(); moveCaretTo (CodeDocument::Position (&document, std::numeric_limits::max(), std::numeric_limits::max()), false); moveCaretTo (CodeDocument::Position (&document, 0, 0), true); } void CodeEditorComponent::undo() { document.undo(); scrollToKeepCaretOnScreen(); } void CodeEditorComponent::redo() { document.redo(); scrollToKeepCaretOnScreen(); } 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) { const bool moveInWholeWordSteps = key.getModifiers().isCtrlDown() || key.getModifiers().isAltDown(); const bool shiftDown = key.getModifiers().isShiftDown(); if (key.isKeyCode (KeyPress::leftKey)) { cursorLeft (moveInWholeWordSteps, shiftDown); } else if (key.isKeyCode (KeyPress::rightKey)) { cursorRight (moveInWholeWordSteps, shiftDown); } else if (key.isKeyCode (KeyPress::upKey)) { if (key.getModifiers().isCtrlDown() && ! shiftDown) scrollDown(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) goToStartOfDocument (shiftDown); #endif else cursorUp (shiftDown); } else if (key.isKeyCode (KeyPress::downKey)) { if (key.getModifiers().isCtrlDown() && ! shiftDown) scrollUp(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) goToEndOfDocument (shiftDown); #endif else cursorDown (shiftDown); } else if (key.isKeyCode (KeyPress::pageDownKey)) { pageDown (shiftDown); } else if (key.isKeyCode (KeyPress::pageUpKey)) { pageUp (shiftDown); } else if (key.isKeyCode (KeyPress::homeKey)) { if (moveInWholeWordSteps) goToStartOfDocument (shiftDown); else goToStartOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::endKey)) { if (moveInWholeWordSteps) goToEndOfDocument (shiftDown); else goToEndOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::backspaceKey)) { backspace (moveInWholeWordSteps); } else if (key.isKeyCode (KeyPress::deleteKey)) { deleteForward (moveInWholeWordSteps); } else if (key == KeyPress ('c', ModifierKeys::commandModifier, 0)) { copy(); } else if (key == KeyPress ('x', ModifierKeys::commandModifier, 0)) { copyThenCut(); } else if (key == KeyPress ('v', ModifierKeys::commandModifier, 0)) { paste(); } else if (key == KeyPress ('z', ModifierKeys::commandModifier, 0)) { undo(); } else if (key == KeyPress ('y', ModifierKeys::commandModifier, 0) || key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)) { redo(); } else if (key == KeyPress ('a', ModifierKeys::commandModifier, 0)) { selectAll(); } else 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) { caret->updatePosition(); } void CodeEditorComponent::focusLost (FocusChangeType) { caret->updatePosition(); } 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 throw() { const String line (document.getLine (lineNum)); jassert (index <= line.length()); int col = 0; for (int i = 0; i < index; ++i) { if (line[i] != '\t') ++col; else col += getTabSize() - (col % getTabSize()); } return col; } int CodeEditorComponent::columnToIndex (int lineNum, int column) const throw() { const String line (document.getLine (lineNum)); const int lineLength = line.length(); int i, col = 0; for (i = 0; i < lineLength; ++i) { if (line[i] != '\t') ++col; else col += getTabSize() - (col % getTabSize()); if (col > column) break; } 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 != 0) { 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 == 0) 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 == 0) 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) throw() { return CharacterFunctions::isLetter (c) || c == '_' || c == '@'; } bool isIdentifierBody (const juce_wchar c) throw() { return CharacterFunctions::isLetterOrDigit (c) || c == '_' || c == '@'; } bool isReservedKeyword (const juce_wchar* const token, const int tokenLength) throw() { static const juce_wchar* const keywords2Char[] = { JUCE_T("if"), JUCE_T("do"), JUCE_T("or"), JUCE_T("id"), 0 }; static const juce_wchar* const keywords3Char[] = { JUCE_T("for"), JUCE_T("int"), JUCE_T("new"), JUCE_T("try"), JUCE_T("xor"), JUCE_T("and"), JUCE_T("asm"), JUCE_T("not"), 0 }; static const juce_wchar* const keywords4Char[] = { JUCE_T("bool"), JUCE_T("void"), JUCE_T("this"), JUCE_T("true"), JUCE_T("long"), JUCE_T("else"), JUCE_T("char"), JUCE_T("enum"), JUCE_T("case"), JUCE_T("goto"), JUCE_T("auto"), 0 }; static const juce_wchar* const keywords5Char[] = { JUCE_T("while"), JUCE_T("bitor"), JUCE_T("break"), JUCE_T("catch"), JUCE_T("class"), JUCE_T("compl"), JUCE_T("const"), JUCE_T("false"), JUCE_T("float"), JUCE_T("short"), JUCE_T("throw"), JUCE_T("union"), JUCE_T("using"), JUCE_T("or_eq"), 0 }; static const juce_wchar* const keywords6Char[] = { JUCE_T("return"), JUCE_T("struct"), JUCE_T("and_eq"), JUCE_T("bitand"), JUCE_T("delete"), JUCE_T("double"), JUCE_T("extern"), JUCE_T("friend"), JUCE_T("inline"), JUCE_T("not_eq"), JUCE_T("public"), JUCE_T("sizeof"), JUCE_T("static"), JUCE_T("signed"), JUCE_T("switch"), JUCE_T("typeid"), JUCE_T("wchar_t"), JUCE_T("xor_eq"), 0}; static const juce_wchar* const keywordsOther[] = { JUCE_T("const_cast"), JUCE_T("continue"), JUCE_T("default"), JUCE_T("explicit"), JUCE_T("mutable"), JUCE_T("namespace"), JUCE_T("operator"), JUCE_T("private"), JUCE_T("protected"), JUCE_T("register"), JUCE_T("reinterpret_cast"), JUCE_T("static_cast"), JUCE_T("template"), JUCE_T("typedef"), JUCE_T("typename"), JUCE_T("unsigned"), JUCE_T("virtual"), JUCE_T("volatile"), JUCE_T("@implementation"), JUCE_T("@interface"), JUCE_T("@end"), JUCE_T("@synthesize"), JUCE_T("@dynamic"), JUCE_T("@public"), JUCE_T("@private"), JUCE_T("@property"), JUCE_T("@protected"), JUCE_T("@class"), 0 }; const juce_wchar* 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 (k[i][0] == token[0] && CharacterFunctions::compare (k[i], token) == 0) return true; ++i; } return false; } int parseIdentifier (CodeDocument::Iterator& source) throw() { int tokenLength = 0; juce_wchar possibleIdentifier [19]; while (isIdentifierBody (source.peekNextChar())) { const juce_wchar c = source.nextChar(); if (tokenLength < numElementsInArray (possibleIdentifier) - 1) possibleIdentifier [tokenLength] = c; ++tokenLength; } if (tokenLength > 1 && tokenLength <= 16) { possibleIdentifier [tokenLength] = 0; if (isReservedKeyword (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) throw() { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } bool parseHexLiteral (CodeDocument::Iterator& source) throw() { 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) throw() { return c >= '0' && c <= '7'; } bool parseOctalLiteral (CodeDocument::Iterator& source) throw() { 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) throw() { return c >= '0' && c <= '9'; } bool parseDecimalLiteral (CodeDocument::Iterator& source) throw() { int numChars = 0; while (isDecimalDigit (source.peekNextChar())) { ++numChars; source.skip(); } if (numChars == 0) return false; return skipNumberSuffix (source); } bool parseFloatLiteral (CodeDocument::Iterator& source) throw() { 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) throw() { 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) throw() { 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) throw() { return CppTokeniser::isReservedKeyword (token, 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 throw() { return name.isEmpty(); } bool ComboBox::ItemInfo::isRealItem() const throw() { 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); lookAndFeelChanged(); currentId.addListener (this); } ComboBox::~ComboBox() { currentId.removeListener (this); if (menuActive) PopupMenu::dismissAllActiveMenus(); label = 0; } 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 throw() { return label->isEditable(); } void ComboBox::setJustificationType (const Justification& justification) { label->setJustificationType (justification); } const Justification ComboBox::getJustificationType() const throw() { 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) == 0); 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 != 0) item->isEnabled = shouldBeEnabled; } void ComboBox::changeItemText (const int itemId, const String& newText) { ItemInfo* const item = getItemForId (itemId); jassert (item != 0); if (item != 0) 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 throw() { if (itemId != 0) { for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->itemId == itemId) return items.getUnchecked(i); } return 0; } ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const throw() { 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 0; } int ComboBox::getNumItems() const throw() { int n = 0; for (int i = items.size(); --i >= 0;) if (items.getUnchecked(i)->isRealItem()) ++n; return n; } const String ComboBox::getItemText (const int index) const { const ItemInfo* const item = getItemForIndex (index); return item != 0 ? item->name : String::empty; } int ComboBox::getItemId (const int index) const throw() { const ItemInfo* const item = getItemForIndex (index); return item != 0 ? item->itemId : 0; } int ComboBox::indexOfItemId (const int itemId) const throw() { 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 throw() { const ItemInfo* const item = getItemForId (currentId.getValue()); return (item != 0 && 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 != 0 ? 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 != 0 && item->isEnabled) { setSelectedItemIndex (index); return true; } return false; } void ComboBox::valueChanged (Value&) { if (lastCurrentId != (int) currentId.getValue()) setSelectedId (currentId.getValue(), false); } const 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(); } } const String ComboBox::getTextWhenNothingSelected() const { return textWhenNothingSelected; } void ComboBox::setTextWhenNoChoicesAvailable (const String& newMessage) { noChoicesMessage = newMessage; } const 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