diff --git a/JUCE/modules/JUCE Module Format.txt b/JUCE/modules/JUCE Module Format.txt new file mode 100644 index 0000000000..ebe41e9faf --- /dev/null +++ b/JUCE/modules/JUCE Module Format.txt @@ -0,0 +1,203 @@ + + The JUCE Module Format + ====================== + +A JUCE module is a collection of header and source files which can be added to a project +to provide a set of classes and libraries or related functionality. + +Their structure is designed to make it as simple as possible for modules to be added to +user projects on many platforms, either via automated tools, or by manual inclusion. + +Each module may have dependencies on other modules, but should be otherwise self-contained. + + File structure + ============== + +Each module lives inside a folder whose name is the same as the name of the module. The +JUCE convention for naming modules is lower-case with underscores, e.g. + +juce_core +juce_events +juce_graphics + +But any name that is a valid C++ identifer is OK. + +Inside the root of this folder, there must be a set of public header and source files which +the user's' project will include. The module may have as many other internal source files as +it needs, but these must all be inside sub-folders! + + +Master header file +------------------ + +In this root folder there must be ONE master header file, which includes all the necessary +header files for the module. This header must have the same name as the module, with +a .h/.hpp/.hxx suffix. E.g. + +juce_core/juce_core.h + +IMPORTANT! All code within a module that includes other files from within its own subfolders +must do so using RELATIVE paths! +A module must be entirely relocatable on disk, and it must not rely on the user's project +having any kind of include path set up correctly for it to work. Even if the user has no +include paths whatsoever and includes the module's master header via an absolute path, +it must still correctly find all of its internally included sub-files. + +This master header file must also contain a comment with a BEGIN_JUCE_MODULE_DECLARATION +block which defines the module's requirements - the syntax for this is described later on.. + + +Module CPP files +---------------- + +A module consists of a single header file and zero or more .cpp files. Fewer is better! + +Ideally, a module could be header-only module, so that a project can use it by simply +including the master header file. + +For various reasons it's usually necessary or preferable to have a simpler header and +some .cpp files that the user's project should compile as stand-alone compile units. +In this case you should ideally provide just a single cpp file in the module's root +folder, and this should internally include all your other cpps from their sub-folders, +so that only a single cpp needs to be added to the user's project in order to completely +compile the module. + +In some cases (e.g. if your module internally relies on 3rd-party code which can't be +easily combined into a single compile-unit) then you may have more than one source file +here, but avoid this if possible, as it will add a burden for users who are manually +adding these files to their projects. + +The names of these source files must begin with the name of the module, but they can have +a number or other suffix if there is more than one. + +In order to specify that a source file should only be compiled on a specific platform, +then the filename can be suffixed with one of the following strings: + +_OSX +_Windows +_Linux +_Android +_iOS + +e.g. +juce_mymodule/juce_mymodule_1.cpp <- compiled on all platforms +juce_mymodule/juce_mymodule_2.cpp <- compiled on all platforms +juce_mymodule/juce_mymodule_OSX.cpp <- compiled only on OSX +juce_mymodule/juce_mymodule_Windows.cpp <- compiled only on Windows + +Often this isn't necessary, as in most cases you can easily add checks inside the files +to do different things depending on the platform, but this may be handy just to avoid +clutter in user projects where files aren't needed. + +To simplify the use of obj-C++ there's also a special-case rule: If the folder contains +both a .mm and a .cpp file whose names are otherwise identical, then on OSX/iOS the .mm +will be used and the cpp ignored. (And vice-versa for other platforms, of course). + + +Precompiled libraries +--------------------- + +Precompiled libraries can be included in a module by placing them in a libs/ subdirectory. +The following directories are automatically added to the library search paths, and libraries +placed in these directories can be linked with projects via the OSXLibs, iOSLibs, +windowsLibs, linuxLibs and mingwLibs keywords in the module declaration (see the following +section). + +OS X: + libs/MacOSX/{arch}, where {arch} is the architecture you are targeting in Xcode ("x86_64" or + "i386", for example). + +Visual Studio: + libs/VisualStudio{year}/{arch}/{run-time}, where {year} is the four digit year of the Visual Studio + release, arch is the target architecture in Visual Studio ("x64" or "Win32", for example), and + {runtime} is the type of the run-time library indicated by the corresponding compiler flag + ("MD", "MDd", "MT", "MTd"). + +Linux: + libs/Linux/{arch}, where {arch} is the architecture you are targeting with the compiler. Some + common examples of {arch} are "x86_64", "i386" and "armv6". + +MinGW: + libs/MinGW/{arch}, where {arch} can take the same values as Linux. + +iOS: + libs/iOS/{arch}, where {arch} is the architecture you are targeting in Xcode ("arm64" or + "x86_64", for example). + +Android: + libs/Android/{arch}, where {arch} is the architecture provided by the Android Studio variable + "${ANDROID_ABI}" ("x86", "armeabi-v7a", "mips", for example). + + The BEGIN_JUCE_MODULE_DECLARATION block + ======================================= + +This block of text needs to go inside the module's main header file. It should be commented-out +and perhaps inside an #if 0 block too, but the Introjucer will just scan the whole file for the +string BEGIN_JUCE_MODULE_DECLARATION, and doesn't care about its context in terms of C++ syntax. + +The block needs a corresponding END_JUCE_MODULE_DECLARATION to finish the block. +These should both be on a line of their own. + +Inside the block, the parser will expect to find a list of value definitions, one-per-line, with +the very simple syntax + + value_name: value + +The value_name must be one of the items listed below, and is case-sensitive. Whitespace on the +line is ignored. Some values are compulsory and must be supplied, but others are optional. +The order in which they're declared doesn't matter. + +Possible values: + + ID: (Compulsory) This ID must match the name of the file and folder, e.g. juce_core. + The main reason for also including it here is as a sanity-check + vendor: (Compulsory) A unique ID for the vendor, e.g. "juce". This should be short + and shouldn't contain any spaces + version: (Compulsory) A version number for the module + name: (Compulsory) A short description of the module + description: (Compulsory) A longer description (but still only one line of text, please!) + + dependencies: (Optional) A list (space or comma-separated) of other modules that are required by + this one. The Introjucer can use this to auto-resolve dependencies. + website: (Optional) A URL linking to useful info about the module] + license: (Optional) A description of the type of software license that applies + minimumCppStandard: (Optional) A number indicating the minimum C++ language standard that is required for this module. + This must be just the standard number with no prefix e.g. 14 for C++14 + searchpaths: (Optional) A space-separated list of internal include paths, relative to the module's + parent folder, which need to be added to a project's header search path + OSXFrameworks: (Optional) A list (space or comma-separated) of OSX frameworks that are needed + by this module + iOSFrameworks: (Optional) Like OSXFrameworks, but for iOS targets + linuxPackages: (Optional) A list (space or comma-separated) pkg-config packages that should be used to pass + compiler (CFLAGS) and linker (LDFLAGS) flags + linuxLibs: (Optional) A list (space or comma-separated) of static or dynamic libs that should be linked in a + linux build (these are passed to the linker via the -l flag) + mingwLibs: (Optional) A list (space or comma-separated) of static libs that should be linked in a + win32 mingw build (these are passed to the linker via the -l flag) + OSXLibs: (Optional) A list (space or comma-separated) of static or dynamic libs that should be linked in an + OS X build (these are passed to the linker via the -l flag) + iOSLibs: (Optional) A list (space or comma-separated) of static or dynamic libs that should be linked in an + iOS build (these are passed to the linker via the -l flag) + windowsLibs: (Optional) A list (space or comma-separated) of static or dynamic libs that should be linked in a + Visual Studio build (without the .lib suffixes) + +Here's an example block: + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_devices + vendor: juce + version: 4.1.0 + name: JUCE audio and MIDI I/O device classes + description: Classes to play and record from audio and MIDI I/O devices + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_audio_basics, juce_audio_formats, juce_events + OSXFrameworks: CoreAudio CoreMIDI DiscRecording + iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation + linuxLibs: asound + mingwLibs: winmm + + END_JUCE_MODULE_DECLARATION + diff --git a/JUCE/modules/juce_analytics/analytics/juce_Analytics.cpp b/JUCE/modules/juce_analytics/analytics/juce_Analytics.cpp new file mode 100644 index 0000000000..22e3b7555a --- /dev/null +++ b/JUCE/modules/juce_analytics/analytics/juce_Analytics.cpp @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +void Analytics::addDestination (AnalyticsDestination* destination) +{ + destinations.add (destination); +} + +OwnedArray& Analytics::getDestinations() +{ + return destinations; +} + +void Analytics::setUserId (String newUserId) +{ + userId = newUserId; +} + +void Analytics::setUserProperties (StringPairArray properties) +{ + userProperties = properties; +} + +void Analytics::logEvent (const String& eventName, + const StringPairArray& parameters, + int eventType) +{ + if (! isSuspended) + { + AnalyticsDestination::AnalyticsEvent event + { + eventName, + eventType, + Time::getMillisecondCounter(), + parameters, + userId, + userProperties + }; + + for (auto* destination : destinations) + destination->logEvent (event); + } +} + +void Analytics::setSuspended (bool shouldBeSuspended) +{ + isSuspended = shouldBeSuspended; +} + +JUCE_IMPLEMENT_SINGLETON (Analytics) + +} diff --git a/JUCE/modules/juce_analytics/analytics/juce_Analytics.h b/JUCE/modules/juce_analytics/analytics/juce_Analytics.h new file mode 100644 index 0000000000..99a88a8b6a --- /dev/null +++ b/JUCE/modules/juce_analytics/analytics/juce_Analytics.h @@ -0,0 +1,117 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A singleton class to manage analytics data. + + Use an Analytics object to manage sending analytics data to one or more + AnalyticsDestinations. + + @see AnalyticsDestination, ThreadedAnalyticsDestination, + AnalyticsDestination::AnalyticsEvent + + @tags{Analytics} +*/ +class JUCE_API Analytics : public DeletedAtShutdown +{ +public: + //============================================================================== + /** Adds an AnalyticsDestination to the list of AnalyticsDestinations + managed by this Analytics object. + + The Analytics class will take ownership of the AnalyticsDestination + passed to this function. + + @param destination the AnalyticsDestination to manage + */ + void addDestination (AnalyticsDestination* destination); + + /** Returns the array of AnalyticsDestinations managed by this class. + + If you have added any subclasses of ThreadedAnalyticsDestination to + this class then you can remove them from this list to force them to + flush any pending events. + */ + OwnedArray& getDestinations(); + + /** Sets a user ID that will be added to all AnalyticsEvents sent to + AnalyticsDestinations. + + @param newUserId the userId to add to AnalyticsEvents + */ + void setUserId (String newUserId); + + /** Sets some user properties that will be added to all AnalyticsEvents sent + to AnalyticsDestinations. + + @param properties the userProperties to add to AnalyticsEvents + */ + void setUserProperties (StringPairArray properties); + + /** Sends an AnalyticsEvent to all AnalyticsDestinations. + + The AnalyticsEvent will be timestamped, and will have the userId and + userProperties populated by values previously set by calls to + setUserId and setUserProperties. The name, parameters and type will be + populated by the arguments supplied to this function. + + @param eventName the event name + @param parameters the event parameters + @param eventType (optional) an integer to indicate the event + type, which will be set to 0 if not supplied. + */ + void logEvent (const String& eventName, const StringPairArray& parameters, int eventType = 0); + + /** Suspends analytics submissions to AnalyticsDestinations. + + @param shouldBeSuspended if event submission should be suspended + */ + void setSuspended (bool shouldBeSuspended); + + #ifndef DOXYGEN + JUCE_DECLARE_SINGLETON (Analytics, false) + #endif + +private: + //============================================================================== + Analytics() = default; + ~Analytics() override { clearSingletonInstance(); } + + String userId; + StringPairArray userProperties; + + bool isSuspended = false; + + OwnedArray destinations; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Analytics) +}; + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.cpp b/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.cpp new file mode 100644 index 0000000000..9ec6776efd --- /dev/null +++ b/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.cpp @@ -0,0 +1,61 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ButtonTracker::ButtonTracker (Button& buttonToTrack, + const String& triggeredEventName, + const StringPairArray& triggeredEventParameters, + int triggeredEventType) + : button (buttonToTrack), + eventName (triggeredEventName), + eventParameters (triggeredEventParameters), + eventType (triggeredEventType) +{ + button.addListener (this); +} + +ButtonTracker::~ButtonTracker() +{ + button.removeListener (this); +} + +void ButtonTracker::buttonClicked (Button* b) +{ + if (b == &button) + { + auto params = eventParameters; + + if (button.getClickingTogglesState()) + params.set ("ButtonState", button.getToggleState() ? "On" : "Off"); + + Analytics::getInstance()->logEvent (eventName, params, eventType); + } +} + + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.h b/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.h new file mode 100644 index 0000000000..a385c6ad3c --- /dev/null +++ b/JUCE/modules/juce_analytics/analytics/juce_ButtonTracker.h @@ -0,0 +1,80 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A class that automatically sends analytics events to the Analytics singleton + when a button is clicked. + + @see Analytics, AnalyticsDestination::AnalyticsEvent + + @tags{Analytics} +*/ +class JUCE_API ButtonTracker : private Button::Listener +{ +public: + //============================================================================== + /** + Creating one of these automatically sends analytics events to the Analytics + singleton when the corresponding button is clicked. + + The name and parameters of the analytics event will be populated from the + variables supplied here. If clicking changes the button's state then the + parameters will have a {"ButtonState", "On"/"Off"} entry added. + + @param buttonToTrack the button to track + @param triggeredEventName the name of the generated event + @param triggeredEventParameters the parameters to add to the generated + event + @param triggeredEventType (optional) an integer to indicate the event + type, which will be set to 0 if not supplied. + + @see Analytics, AnalyticsDestination::AnalyticsEvent + */ + ButtonTracker (Button& buttonToTrack, + const String& triggeredEventName, + const StringPairArray& triggeredEventParameters = {}, + int triggeredEventType = 0); + + /** Destructor. */ + ~ButtonTracker() override; + +private: + /** @internal */ + void buttonClicked (Button*) override; + + Button& button; + const String eventName; + const StringPairArray eventParameters; + const int eventType; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonTracker) +}; + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/destinations/juce_AnalyticsDestination.h b/JUCE/modules/juce_analytics/destinations/juce_AnalyticsDestination.h new file mode 100644 index 0000000000..d0855d8238 --- /dev/null +++ b/JUCE/modules/juce_analytics/destinations/juce_AnalyticsDestination.h @@ -0,0 +1,98 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An interface for handling analytics events collected by an Analytics object. + + For basic analytics logging you can implement this interface and add your + class to an Analytics object. + + For more advanced logging you may want to subclass + ThreadedAnalyticsDestination instead, which is more suitable for interacting + with web servers and other time consuming destinations. + + @see Analytics, ThreadedAnalyticsDestination + + @tags{Analytics} +*/ +struct JUCE_API AnalyticsDestination +{ + /** Contains information about an event to be logged. */ + struct AnalyticsEvent + { + /** The name of the event. */ + String name; + + /** An optional integer representing the type of the event. You can use + this to indicate if the event was a screenview, session start, + exception, etc. + */ + int eventType; + + /** + The timestamp of the event. + + Timestamps are automatically applied by an Analytics object and are + derived from Time::getMillisecondCounter(). As such these timestamps + do not represent absolute times, but relative timings of events for + each user in each session will be accurate. + */ + uint32 timestamp; + + /** The parameters of the event. */ + StringPairArray parameters; + + /** The user ID associated with the event. */ + String userID; + + /** Properties associated with the user. */ + StringPairArray userProperties; + }; + + /** Constructor. */ + AnalyticsDestination() = default; + + /** Destructor. */ + virtual ~AnalyticsDestination() = default; + + /** + When an AnalyticsDestination is added to an Analytics object this method + is called when an analytics event is triggered from the Analytics + object. + + Override this method to log the event information somewhere useful. + */ + virtual void logEvent (const AnalyticsEvent& event) = 0; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsDestination) +}; + + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp b/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp new file mode 100644 index 0000000000..7c92bbba92 --- /dev/null +++ b/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp @@ -0,0 +1,292 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ThreadedAnalyticsDestination::ThreadedAnalyticsDestination (const String& threadName) + : dispatcher (threadName, *this) +{} + +ThreadedAnalyticsDestination::~ThreadedAnalyticsDestination() +{ + // If you hit this assertion then the analytics thread has not been shut down + // before this class is destroyed. Call stopAnalyticsThread() in your destructor! + jassert (! dispatcher.isThreadRunning()); +} + +void ThreadedAnalyticsDestination::setBatchPeriod (int newBatchPeriodMilliseconds) +{ + dispatcher.batchPeriodMilliseconds = newBatchPeriodMilliseconds; +} + +void ThreadedAnalyticsDestination::logEvent (const AnalyticsEvent& event) +{ + dispatcher.addToQueue (event); +} + +void ThreadedAnalyticsDestination::startAnalyticsThread (int initialBatchPeriodMilliseconds) +{ + setBatchPeriod (initialBatchPeriodMilliseconds); + dispatcher.startThread(); +} + +void ThreadedAnalyticsDestination::stopAnalyticsThread (int timeout) +{ + dispatcher.signalThreadShouldExit(); + stopLoggingEvents(); + dispatcher.stopThread (timeout); + + if (dispatcher.eventQueue.size() > 0) + saveUnloggedEvents (dispatcher.eventQueue); +} + +ThreadedAnalyticsDestination::EventDispatcher::EventDispatcher (const String& dispatcherThreadName, + ThreadedAnalyticsDestination& destination) + : Thread (dispatcherThreadName), + parent (destination) +{} + +void ThreadedAnalyticsDestination::EventDispatcher::run() +{ + // We may have inserted some events into the queue (on the message thread) + // before this thread has started, so make sure the old events are at the + // front of the queue. + { + std::deque restoredEventQueue; + parent.restoreUnloggedEvents (restoredEventQueue); + + const ScopedLock lock (queueAccess); + + for (auto rit = restoredEventQueue.rbegin(); rit != restoredEventQueue.rend(); ++rit) + eventQueue.push_front (*rit); + } + + const int maxBatchSize = parent.getMaximumBatchSize(); + + while (! threadShouldExit()) + { + { + const auto numEventsInBatch = eventsToSend.size(); + const auto freeBatchCapacity = maxBatchSize - numEventsInBatch; + + if (freeBatchCapacity > 0) + { + const auto numNewEvents = (int) eventQueue.size() - numEventsInBatch; + + if (numNewEvents > 0) + { + const ScopedLock lock (queueAccess); + + const auto numEventsToAdd = jmin (numNewEvents, freeBatchCapacity); + const auto newBatchSize = numEventsInBatch + numEventsToAdd; + + for (auto i = numEventsInBatch; i < newBatchSize; ++i) + eventsToSend.add (eventQueue[(size_t) i]); + } + } + } + + const auto submissionTime = Time::getMillisecondCounter(); + + if (! eventsToSend.isEmpty()) + { + if (parent.logBatchedEvents (eventsToSend)) + { + const ScopedLock lock (queueAccess); + + for (auto i = 0; i < eventsToSend.size(); ++i) + eventQueue.pop_front(); + + eventsToSend.clearQuick(); + } + } + + while (Time::getMillisecondCounter() - submissionTime < (uint32) batchPeriodMilliseconds.get()) + { + if (threadShouldExit()) + return; + + Thread::sleep (100); + } + } +} + +void ThreadedAnalyticsDestination::EventDispatcher::addToQueue (const AnalyticsEvent& event) +{ + const ScopedLock lock (queueAccess); + eventQueue.push_back (event); +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +namespace DestinationTestHelpers +{ + //============================================================================== + struct BasicDestination : public ThreadedAnalyticsDestination + { + BasicDestination (std::deque& loggedEvents, + std::deque& unloggedEvents) + : ThreadedAnalyticsDestination ("ThreadedAnalyticsDestinationTest"), + loggedEventQueue (loggedEvents), + unloggedEventStore (unloggedEvents) + { + startAnalyticsThread (20); + } + + ~BasicDestination() override + { + stopAnalyticsThread (1000); + } + + int getMaximumBatchSize() override + { + return 5; + } + + void saveUnloggedEvents (const std::deque& eventsToSave) override + { + unloggedEventStore = eventsToSave; + } + + void restoreUnloggedEvents (std::deque& restoredEventQueue) override + { + restoredEventQueue = unloggedEventStore; + } + + bool logBatchedEvents (const Array& events) override + { + jassert (events.size() <= getMaximumBatchSize()); + + if (loggingIsEnabled) + { + const ScopedLock lock (eventQueueChanging); + + for (auto& event : events) + loggedEventQueue.push_back (event); + + return true; + } + + return false; + } + + void stopLoggingEvents() override {} + + void setLoggingEnabled (bool shouldLogEvents) + { + loggingIsEnabled = shouldLogEvents; + } + + std::deque& loggedEventQueue; + std::deque& unloggedEventStore; + bool loggingIsEnabled = true; + CriticalSection eventQueueChanging; + }; +} + +//============================================================================== +struct ThreadedAnalyticsDestinationTests : public UnitTest +{ + ThreadedAnalyticsDestinationTests() + : UnitTest ("ThreadedAnalyticsDestination", UnitTestCategories::analytics) + {} + + void compareEventQueues (const std::deque& a, + const std::deque& b) + { + const auto numEntries = a.size(); + expectEquals ((int) b.size(), (int) numEntries); + + for (size_t i = 0; i < numEntries; ++i) + { + expectEquals (a[i].name, b[i].name); + expect (a[i].timestamp == b[i].timestamp); + } + } + + void runTest() override + { + std::deque testEvents; + + for (int i = 0; i < 7; ++i) + testEvents.push_back ({ String (i), 0, Time::getMillisecondCounter(), {}, "TestUser", {} }); + + std::deque loggedEvents, unloggedEvents; + + beginTest ("New events"); + + { + DestinationTestHelpers::BasicDestination destination (loggedEvents, unloggedEvents); + + for (auto& event : testEvents) + destination.logEvent (event); + + size_t waitTime = 0, numLoggedEvents = 0; + + while (numLoggedEvents < testEvents.size()) + { + if (waitTime > 4000) + { + expect (waitTime < 4000); + break; + } + + Thread::sleep (40); + waitTime += 40; + + const ScopedLock lock (destination.eventQueueChanging); + numLoggedEvents = loggedEvents.size(); + } + } + + compareEventQueues (loggedEvents, testEvents); + expect (unloggedEvents.size() == 0); + + loggedEvents.clear(); + + beginTest ("Unlogged events"); + { + DestinationTestHelpers::BasicDestination destination (loggedEvents, unloggedEvents); + destination.setLoggingEnabled (false); + + for (auto& event : testEvents) + destination.logEvent (event); + } + + compareEventQueues (unloggedEvents, testEvents); + expect (loggedEvents.size() == 0); + } +}; + +static ThreadedAnalyticsDestinationTests threadedAnalyticsDestinationTests; + +#endif + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.h b/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.h new file mode 100644 index 0000000000..d6d0182f25 --- /dev/null +++ b/JUCE/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.h @@ -0,0 +1,218 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A base class for dispatching analytics events on a dedicated thread. + + This class is particularly useful for sending analytics events to a web + server without blocking the message thread. It can also save (and restore) + events that were not dispatched so no information is lost when an internet + connection is absent or something else prevents successful logging. + + Once startAnalyticsThread is called the logBatchedEvents method is + periodically invoked on an analytics thread, with the period determined by + calls to setBatchPeriod. Here events are grouped together into batches, with + the maximum batch size set by the implementation of getMaximumBatchSize. + + It's important to call stopAnalyticsThread in the destructor of your + subclass (or before then) to give the analytics thread time to shut down. + Calling stopAnalyticsThread will, in turn, call stopLoggingEvents, which + you should use to terminate the currently running logBatchedEvents call. + + @see Analytics, AnalyticsDestination, AnalyticsDestination::AnalyticsEvent + + @tags{Analytics} +*/ +class JUCE_API ThreadedAnalyticsDestination : public AnalyticsDestination +{ +public: + //============================================================================== + /** + Creates a ThreadedAnalyticsDestination. + + @param threadName used to identify the analytics + thread in debug builds + */ + ThreadedAnalyticsDestination (const String& threadName = "Analytics thread"); + + /** Destructor. */ + ~ThreadedAnalyticsDestination() override; + + //============================================================================== + /** + Override this method to provide the maximum batch size you can handle in + your subclass. + + Calls to logBatchedEvents will contain no more than this number of events. + */ + virtual int getMaximumBatchSize() = 0; + + /** + This method will be called periodically on the analytics thread. + + If this method returns false then the subsequent call of this function will + contain the same events as previous call, plus any new events that have been + generated in the period between calls. The order of events will not be + changed. This allows you to retry logging events until they are logged + successfully. + + @param events a list of events to be logged + @returns if the events were successfully logged + */ + virtual bool logBatchedEvents (const Array& events) = 0; + + /** + You must always call stopAnalyticsThread in the destructor of your subclass + (or before then) to give the analytics thread time to shut down. + + Calling stopAnalyticsThread triggers a call to this method. At this point + you are guaranteed that logBatchedEvents has been called for the last time + and you should make sure that the current call to logBatchedEvents finishes + as quickly as possible. This method and a subsequent call to + saveUnloggedEvents must both complete before the timeout supplied to + stopAnalyticsThread. + + In a normal use case stopLoggingEvents will be called on the message thread + from the destructor of your ThreadedAnalyticsDestination subclass, and must + stop the logBatchedEvents method which is running on the analytics thread. + + @see stopAnalyticsThread + */ + virtual void stopLoggingEvents() = 0; + + //============================================================================== + /** + Call this to set the period between logBatchedEvents invocations. + + This method is thread safe and can be used to implements things like + exponential backoff in logBatchedEvents calls. + + @param newSubmissionPeriodMilliseconds the new submission period to + use in milliseconds + */ + void setBatchPeriod (int newSubmissionPeriodMilliseconds); + + /** + Adds an event to the queue, which will ultimately be submitted to + logBatchedEvents. + + This method is thread safe. + + @param event the analytics event to add to the queue + */ + void logEvent (const AnalyticsEvent& event) override final; + +protected: + //============================================================================== + /** + Starts the analytics thread, with an initial event batching period. + + @param initialBatchPeriodMilliseconds the initial event batching period + in milliseconds + */ + void startAnalyticsThread (int initialBatchPeriodMilliseconds); + + //============================================================================== + /** + Triggers the shutdown of the analytics thread. + + You must call this method in the destructor of your subclass (or before + then) to give the analytics thread time to shut down. + + This method invokes stopLoggingEvents and you should ensure that both the + analytics thread and a call to saveUnloggedEvents are able to finish before + the supplied timeout. This timeout is important because on platforms like + iOS an app is killed if it takes too long to shut down. + + @param timeoutMilliseconds the number of milliseconds before + the analytics thread is forcibly + terminated + */ + void stopAnalyticsThread (int timeoutMilliseconds); + +private: + //============================================================================== + /** + This method will be called when the analytics thread is shut down, + giving you the chance to save any analytics events that could not be + logged. Once saved these events can be put back into the queue of events + when the ThreadedAnalyticsDestination is recreated via + restoreUnloggedEvents. + + This method should return as quickly as possible, as both + stopLoggingEvents and this method need to complete inside the timeout + set in stopAnalyticsThread. + + @param eventsToSave the events that could not be logged + + @see stopAnalyticsThread, stopLoggingEvents, restoreUnloggedEvents + */ + virtual void saveUnloggedEvents (const std::deque& eventsToSave) = 0; + + /** + The counterpart to saveUnloggedEvents. + + Events added to the event queue provided by this method will be the + first events supplied to logBatchedEvents calls. Use this method to + restore any unlogged events previously stored in a call to + saveUnloggedEvents. + + This method is called on the analytics thread. + + @param restoredEventQueue place restored events into this queue + + @see saveUnloggedEvents + */ + virtual void restoreUnloggedEvents (std::deque& restoredEventQueue) = 0; + + struct EventDispatcher : public Thread + { + EventDispatcher (const String& threadName, ThreadedAnalyticsDestination&); + + void run() override; + void addToQueue (const AnalyticsEvent&); + + ThreadedAnalyticsDestination& parent; + + std::deque eventQueue; + CriticalSection queueAccess; + + Atomic batchPeriodMilliseconds { 1000 }; + + Array eventsToSend; + }; + + const String destinationName; + EventDispatcher dispatcher; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadedAnalyticsDestination) +}; + +} // namespace juce diff --git a/JUCE/modules/juce_analytics/juce_analytics.cpp b/JUCE/modules/juce_analytics/juce_analytics.cpp new file mode 100644 index 0000000000..f66a20495d --- /dev/null +++ b/JUCE/modules/juce_analytics/juce_analytics.cpp @@ -0,0 +1,40 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#ifdef JUCE_ANALYTICS_H_INCLUDED + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +#include "juce_analytics.h" + +#include "destinations/juce_ThreadedAnalyticsDestination.cpp" +#include "analytics/juce_Analytics.cpp" +#include "analytics/juce_ButtonTracker.cpp" diff --git a/JUCE/modules/juce_analytics/juce_analytics.h b/JUCE/modules/juce_analytics/juce_analytics.h new file mode 100644 index 0000000000..36828075a0 --- /dev/null +++ b/JUCE/modules/juce_analytics/juce_analytics.h @@ -0,0 +1,61 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_analytics + vendor: juce + version: 5.4.7 + name: JUCE analytics classes + description: Classes to collect analytics and send to destinations + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_gui_basics + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + +#pragma once +#define JUCE_ANALYTICS_H_INCLUDED + +#include +#include + +#include "destinations/juce_AnalyticsDestination.h" +#include "destinations/juce_ThreadedAnalyticsDestination.h" +#include "analytics/juce_Analytics.h" +#include "analytics/juce_ButtonTracker.h" diff --git a/JUCE/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h b/JUCE/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h index 8a58c7ba97..70f3a2b4e8 100644 --- a/JUCE/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/JUCE/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -39,10 +39,10 @@ class JUCE_API AudioPlayHead { protected: //============================================================================== - AudioPlayHead() {} + AudioPlayHead() = default; public: - virtual ~AudioPlayHead() {} + virtual ~AudioPlayHead() = default; //============================================================================== /** Frame rate types. */ diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp b/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp index 4ca398ca5d..40ba299231 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp @@ -75,6 +75,27 @@ String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); case topSideLeft: return NEEDS_TRANS("Top Side Left"); case topSideRight: return NEEDS_TRANS("Top Side Right"); + case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4"); + case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5"); + case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6"); + case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7"); + case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8"); + case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9"); + case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10"); + case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11"); + case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12"); + case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13"); + case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14"); + case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15"); + case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); + case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); + case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); + case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left"); + case bottomSideRight: return NEEDS_TRANS("Bottom Side Right"); + case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left"); + case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre"); + case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right"); + case discreteChannel0: return NEEDS_TRANS("Discrete channel"); default: break; } @@ -115,8 +136,28 @@ String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelT case ambisonicACN1: return "ACN1"; case ambisonicACN2: return "ACN2"; case ambisonicACN3: return "ACN3"; + case ambisonicACN4: return "ACN4"; + case ambisonicACN5: return "ACN5"; + case ambisonicACN6: return "ACN6"; + case ambisonicACN7: return "ACN7"; + case ambisonicACN8: return "ACN8"; + case ambisonicACN9: return "ACN9"; + case ambisonicACN10: return "ACN10"; + case ambisonicACN11: return "ACN11"; + case ambisonicACN12: return "ACN12"; + case ambisonicACN13: return "ACN13"; + case ambisonicACN14: return "ACN14"; + case ambisonicACN15: return "ACN15"; case topSideLeft: return "Tsl"; case topSideRight: return "Tsr"; + case bottomFrontLeft: return "Bfl"; + case bottomFrontCentre: return "Bfc"; + case bottomFrontRight: return "Bfr"; + case bottomSideLeft: return "Bsl"; + case bottomSideRight: return "Bsr"; + case bottomRearLeft: return "Brl"; + case bottomRearCentre: return "Brc"; + case bottomRearRight: return "Brr"; default: break; } @@ -130,38 +171,61 @@ AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (co { if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) return static_cast (static_cast (discreteChannel0) - + abbr.getIntValue() + 1); - - if (abbr == "L") return left; - if (abbr == "R") return right; - if (abbr == "C") return centre; - if (abbr == "Lfe") return LFE; - if (abbr == "Ls") return leftSurround; - if (abbr == "Rs") return rightSurround; - if (abbr == "Lc") return leftCentre; - if (abbr == "Rc") return rightCentre; - if (abbr == "Cs") return centreSurround; - if (abbr == "Lrs") return leftSurroundRear; - if (abbr == "Rrs") return rightSurroundRear; - if (abbr == "Tm") return topMiddle; - if (abbr == "Tfl") return topFrontLeft; - if (abbr == "Tfc") return topFrontCentre; - if (abbr == "Tfr") return topFrontRight; - if (abbr == "Trl") return topRearLeft; - if (abbr == "Trc") return topRearCentre; - if (abbr == "Trr") return topRearRight; - if (abbr == "Wl") return wideLeft; - if (abbr == "Wr") return wideRight; - if (abbr == "Lfe2") return LFE2; - if (abbr == "Lss") return leftSurroundSide; - if (abbr == "Rss") return rightSurroundSide; - if (abbr == "W") return ambisonicW; - if (abbr == "X") return ambisonicX; - if (abbr == "Y") return ambisonicY; - if (abbr == "Z") return ambisonicZ; - if (abbr == "Tsl") return topSideLeft; - if (abbr == "Tsr") return topSideRight; - + + abbr.getIntValue() - 1); + + if (abbr == "L") return left; + if (abbr == "R") return right; + if (abbr == "C") return centre; + if (abbr == "Lfe") return LFE; + if (abbr == "Ls") return leftSurround; + if (abbr == "Rs") return rightSurround; + if (abbr == "Lc") return leftCentre; + if (abbr == "Rc") return rightCentre; + if (abbr == "Cs") return centreSurround; + if (abbr == "Lrs") return leftSurroundRear; + if (abbr == "Rrs") return rightSurroundRear; + if (abbr == "Tm") return topMiddle; + if (abbr == "Tfl") return topFrontLeft; + if (abbr == "Tfc") return topFrontCentre; + if (abbr == "Tfr") return topFrontRight; + if (abbr == "Trl") return topRearLeft; + if (abbr == "Trc") return topRearCentre; + if (abbr == "Trr") return topRearRight; + if (abbr == "Wl") return wideLeft; + if (abbr == "Wr") return wideRight; + if (abbr == "Lfe2") return LFE2; + if (abbr == "Lss") return leftSurroundSide; + if (abbr == "Rss") return rightSurroundSide; + if (abbr == "W") return ambisonicW; + if (abbr == "X") return ambisonicX; + if (abbr == "Y") return ambisonicY; + if (abbr == "Z") return ambisonicZ; + if (abbr == "ACN0") return ambisonicACN0; + if (abbr == "ACN1") return ambisonicACN1; + if (abbr == "ACN2") return ambisonicACN2; + if (abbr == "ACN3") return ambisonicACN3; + if (abbr == "ACN4") return ambisonicACN4; + if (abbr == "ACN5") return ambisonicACN5; + if (abbr == "ACN6") return ambisonicACN6; + if (abbr == "ACN7") return ambisonicACN7; + if (abbr == "ACN8") return ambisonicACN8; + if (abbr == "ACN9") return ambisonicACN9; + if (abbr == "ACN10") return ambisonicACN10; + if (abbr == "ACN11") return ambisonicACN11; + if (abbr == "ACN12") return ambisonicACN12; + if (abbr == "ACN13") return ambisonicACN13; + if (abbr == "ACN14") return ambisonicACN14; + if (abbr == "ACN15") return ambisonicACN15; + if (abbr == "Tsl") return topSideLeft; + if (abbr == "Tsr") return topSideRight; + if (abbr == "Bfl") return bottomFrontLeft; + if (abbr == "Bfc") return bottomFrontCentre; + if (abbr == "Bfr") return bottomFrontRight; + if (abbr == "Bsl") return bottomSideLeft; + if (abbr == "Bsr") return bottomSideRight; + if (abbr == "Brl") return bottomRearLeft; + if (abbr == "Brc") return bottomRearCentre; + if (abbr == "Brr") return bottomRearRight; return unknown; } @@ -493,12 +557,17 @@ int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChann return (static_cast (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1); } + +//============================================================================== //============================================================================== #if JUCE_UNIT_TESTS + class AudioChannelSetUnitTest : public UnitTest { public: - AudioChannelSetUnitTest() : UnitTest ("AudioChannelSetUnitTest", "Audio") {} + AudioChannelSetUnitTest() + : UnitTest ("AudioChannelSetUnitTest", UnitTestCategories::audio) + {} void runTest() override { @@ -581,6 +650,7 @@ class AudioChannelSetUnitTest : public UnitTest }; static AudioChannelSetUnitTest audioChannelSetUnitTest; + #endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h index 719e029ff8..e9985a18fa 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -49,7 +49,7 @@ class JUCE_API AudioChannelSet /** Creates an empty channel set. You can call addChannel to add channels to the set. */ - AudioChannelSet() noexcept {} + AudioChannelSet() = default; /** Creates a zero-channel set which can be used to indicate that a bus is disabled. */ @@ -365,7 +365,21 @@ class JUCE_API AudioChannelSet ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */ //============================================================================== - discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ + bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */ + bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */ + bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */ + + proxymityLeft = 65, /**< Proximity Left (Pl) */ + proximityRight = 66, /**< Proximity Right (Pr) */ + + bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */ + bottomSideRight = 68, /**< Bottom Side Right (Bsr) */ + bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */ + bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ + bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ + + //============================================================================== + discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ }; /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp b/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp index 4b7b4683a9..a441a8f0eb 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp @@ -23,16 +23,16 @@ namespace juce { -void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fff; + auto 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]))); + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -43,21 +43,21 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *reinterpret_cast (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) +void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fff; + auto 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]))); + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -68,15 +68,15 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *reinterpret_cast (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) +void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fffff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fffff; + auto intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { @@ -98,10 +98,10 @@ void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest } } -void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fffff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fffff; + auto intData = static_cast (dest); if (dest != (void*) source || destBytesPerSample <= 4) { @@ -123,16 +123,16 @@ void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest } } -void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fffffff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fffffff; + auto 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]))); + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -143,21 +143,21 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } -void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) { - const double maxVal = (double) 0x7fffffff; - char* intData = static_cast (dest); + auto maxVal = (double) 0x7fffffff; + auto 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]))); + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -168,12 +168,12 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } -void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! @@ -181,28 +181,28 @@ void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* de for (int i = 0; i < numSamples; ++i) { - *(float*) d = source[i]; + *reinterpret_cast (d) = source[i]; #if JUCE_BIG_ENDIAN - *(uint32*) d = ByteOrder::swap (*(uint32*) d); + *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); #endif d += destBytesPerSample; } } -void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) +void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) { jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! - char* d = static_cast (dest); + auto d = static_cast (dest); for (int i = 0; i < numSamples; ++i) { - *(float*) d = source[i]; + *reinterpret_cast (d) = source[i]; #if JUCE_LITTLE_ENDIAN - *(uint32*) d = ByteOrder::swap (*(uint32*) d); + *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); #endif d += destBytesPerSample; @@ -210,16 +210,16 @@ void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* de } //============================================================================== -void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; - const char* intData = static_cast (source); + auto 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); + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); intData += srcBytesPerSample; } } @@ -230,21 +230,21 @@ void AudioDataConverters::convertInt16LEToFloat (const void* const source, float for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); } } } -void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; - const char* intData = static_cast (source); + auto 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); + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); intData += srcBytesPerSample; } } @@ -255,15 +255,15 @@ void AudioDataConverters::convertInt16BEToFloat (const void* const source, float for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); } } } -void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; - const char* intData = static_cast (source); + auto intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { @@ -285,10 +285,10 @@ void AudioDataConverters::convertInt24LEToFloat (const void* const source, float } } -void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; - const char* intData = static_cast (source); + auto intData = static_cast (source); if (source != (void*) dest || srcBytesPerSample >= 4) { @@ -310,16 +310,16 @@ void AudioDataConverters::convertInt24BEToFloat (const void* const source, float } } -void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { - const auto scale = 1.0f / (float) 0x7fffffff; - const char* intData = static_cast (source); + const float scale = 1.0f / (float) 0x7fffffff; + auto 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); + dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); intData += srcBytesPerSample; } } @@ -330,21 +330,21 @@ void AudioDataConverters::convertInt32LEToFloat (const void* const source, float for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); + dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); } } } -void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { - const auto scale = 1.0f / (float) 0x7fffffff; - const char* intData = static_cast (source); + const float scale = 1.0f / (float) 0x7fffffff; + auto 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); + dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); intData += srcBytesPerSample; } } @@ -355,21 +355,21 @@ void AudioDataConverters::convertInt32BEToFloat (const void* const source, float for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); + dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); } } } -void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { - const char* s = static_cast (source); + auto s = static_cast (source); for (int i = 0; i < numSamples; ++i) { - dest[i] = *(float*)s; + dest[i] = *reinterpret_cast (s); #if JUCE_BIG_ENDIAN - uint32* const d = (uint32*) (dest + i); + auto d = reinterpret_cast (dest + i); *d = ByteOrder::swap (*d); #endif @@ -377,16 +377,16 @@ void AudioDataConverters::convertFloat32LEToFloat (const void* const source, flo } } -void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) +void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) { - const char* s = static_cast (source); + auto s = static_cast (source); for (int i = 0; i < numSamples; ++i) { - dest[i] = *(float*)s; + dest[i] = *reinterpret_cast (s); #if JUCE_LITTLE_ENDIAN - uint32* const d = (uint32*) (dest + i); + auto d = reinterpret_cast (dest + i); *d = ByteOrder::swap (*d); #endif @@ -396,10 +396,7 @@ void AudioDataConverters::convertFloat32BEToFloat (const void* const source, flo //============================================================================== -void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, - const float* const source, - void* const dest, - const int numSamples) +void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, int numSamples) { switch (destFormat) { @@ -415,10 +412,7 @@ void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, } } -void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, - const void* const source, - float* const dest, - const int numSamples) +void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const void* source, float* dest, int numSamples) { switch (sourceFormat) { @@ -435,15 +429,12 @@ void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, } //============================================================================== -void AudioDataConverters::interleaveSamples (const float** const source, - float* const dest, - const int numSamples, - const int numChannels) +void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { - int i = chan; - const float* src = source [chan]; + auto i = chan; + auto src = source [chan]; for (int j = 0; j < numSamples; ++j) { @@ -453,15 +444,12 @@ void AudioDataConverters::interleaveSamples (const float** const source, } } -void AudioDataConverters::deinterleaveSamples (const float* const source, - float** const dest, - const int numSamples, - const int numChannels) +void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) { for (int chan = 0; chan < numChannels; ++chan) { - int i = chan; - float* dst = dest [chan]; + auto i = chan; + auto dst = dest [chan]; for (int j = 0; j < numSamples; ++j) { @@ -472,13 +460,16 @@ void AudioDataConverters::deinterleaveSamples (const float* const source, } +//============================================================================== //============================================================================== #if JUCE_UNIT_TESTS class AudioConversionTests : public UnitTest { public: - AudioConversionTests() : UnitTest ("Audio data conversion", "Audio") {} + AudioConversionTests() + : UnitTest ("Audio data conversion", UnitTestCategories::audio) + {} template struct Test5 @@ -492,7 +483,9 @@ class AudioConversionTests : public UnitTest static void test (UnitTest& unitTest, bool inPlace, Random& r) { const int numSamples = 2048; - int32 original [numSamples], converted [numSamples], reversed [numSamples]; + int32 original [(size_t) numSamples], + converted[(size_t) numSamples], + reversed [(size_t) numSamples]; { AudioData::Pointer d (original); @@ -514,13 +507,13 @@ class AudioConversionTests : public UnitTest } // convert data from the source to dest format.. - std::unique_ptr conv (new AudioData::ConverterInstance , - AudioData::Pointer>()); + std::unique_ptr conv (new AudioData::ConverterInstance, + AudioData::Pointer>()); conv->convertSamples (inPlace ? reversed : converted, original, numSamples); // ..and back again.. - conv.reset (new AudioData::ConverterInstance , - AudioData::Pointer>()); + conv.reset (new AudioData::ConverterInstance, + AudioData::Pointer>()); if (! inPlace) zeromem (reversed, sizeof (reversed)); @@ -582,7 +575,7 @@ class AudioConversionTests : public UnitTest void runTest() override { - Random r = getRandom(); + auto r = getRandom(); beginTest ("Round-trip conversion: Int8"); Test1 ::test (*this, r); beginTest ("Round-trip conversion: Int16"); diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h b/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h index 45c6c3099f..e8e1fed627 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h @@ -102,9 +102,9 @@ class JUCE_API AudioData inline void advance() noexcept { ++data; } inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + maxValue))); } + inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + (double) maxValue))); } inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } - inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))); } + inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))); } inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); } inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } @@ -127,9 +127,9 @@ class JUCE_API AudioData inline void advance() noexcept { ++data; } inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + maxValue))); } + inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + (double) maxValue))); } inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } - inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + maxValue))); } + inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + (double) maxValue))); } inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } inline int32 getAsInt32LE() const noexcept { return (int) (((uint8) (*data - 128)) << 24); } inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } @@ -152,10 +152,10 @@ class JUCE_API AudioData inline void advance() noexcept { ++data; } inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } @@ -177,10 +177,10 @@ class JUCE_API AudioData inline void advance() noexcept { data += 3; } inline void skip (int numSamples) noexcept { data += 3 * numSamples; } - inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } - inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } - inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } - inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } + inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } + inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } + inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } + inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } inline int32 getAsInt32LE() const noexcept { return (int32) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) ByteOrder::bigEndian24Bit (data)) << 8); } inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } @@ -202,10 +202,10 @@ class JUCE_API AudioData inline void advance() noexcept { ++data; } inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } @@ -226,10 +226,10 @@ class JUCE_API AudioData public: inline Int24in32 (void* d) noexcept : Int32 (d) {} - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } @@ -261,8 +261,8 @@ class JUCE_API AudioData #endif inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } - inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } - inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } + inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } + inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } inline void clear() noexcept { *data = 0; } inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} template inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } @@ -277,8 +277,8 @@ class JUCE_API AudioData class NonInterleaved { public: - inline NonInterleaved() noexcept {} - inline NonInterleaved (const NonInterleaved&) noexcept {} + inline NonInterleaved() = default; + inline NonInterleaved (const NonInterleaved&) = default; inline NonInterleaved (const int) noexcept {} inline void copyFrom (const NonInterleaved&) noexcept {} template inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } @@ -292,15 +292,15 @@ class JUCE_API AudioData class Interleaved { public: - inline Interleaved() noexcept : numInterleavedChannels (1) {} - inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {} + inline Interleaved() noexcept {} + inline Interleaved (const Interleaved& other) = default; inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } template inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } template inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } template inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } template inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } - int numInterleavedChannels; + int numInterleavedChannels = 1; enum { isInterleavedType = 1 }; }; @@ -528,8 +528,8 @@ class JUCE_API AudioData if (v < mn) mn = v; } - return Range (mn * (float) (1.0 / (1.0 + Int32::maxValue)), - mx * (float) (1.0 / (1.0 + Int32::maxValue))); + return Range (mn * (float) (1.0 / (1.0 + (double) Int32::maxValue)), + mx * (float) (1.0 / (1.0 + (double) Int32::maxValue))); } /** Scans a block of data, returning the lowest and highest levels as floats */ @@ -587,7 +587,7 @@ class JUCE_API AudioData class Converter { public: - virtual ~Converter() {} + virtual ~Converter() = default; /** Converts a sequence of samples from the converter's source format into the dest format. */ virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp b/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp new file mode 100644 index 0000000000..1795949351 --- /dev/null +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() {} +AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() {} + +void AudioProcessLoadMeasurer::reset() +{ + reset (0, 0); +} + +void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) +{ + cpuUsageMs = 0; + xruns = 0; + + if (sampleRate > 0.0 && blockSize > 0) + { + msPerBlock = 1000.0 * blockSize / sampleRate; + timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; + } + else + { + msPerBlock = 0; + timeToCpuScale = 0; + } +} + +void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) +{ + const double filterAmount = 0.2; + cpuUsageMs += filterAmount * (milliseconds - cpuUsageMs); + + if (milliseconds > msPerBlock) + ++xruns; +} + +double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } +double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } + +int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } + +AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) + : owner (p), startTime (Time::getMillisecondCounterHiRes()) +{ +} + +AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() +{ + owner.registerBlockRenderTime (Time::getMillisecondCounterHiRes() - startTime); +} + +} // namespace juce diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h b/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h new file mode 100644 index 0000000000..8cf5970686 --- /dev/null +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Maintains an ongoing measurement of the proportion of time which is being + spent inside an audio callback. + + @tags{Audio} +*/ +class JUCE_API AudioProcessLoadMeasurer +{ +public: + /** */ + AudioProcessLoadMeasurer(); + + /** Destructor. */ + ~AudioProcessLoadMeasurer(); + + //============================================================================== + /** Resets the state. */ + void reset(); + + /** Resets the counter, in preparation for use with the given sample rate and block size. */ + void reset (double sampleRate, int blockSize); + + /** Returns the current load as a proportion 0 to 1.0 */ + double getLoadAsProportion() const; + + /** Returns the current load as a percentage 0 to 100.0 */ + double getLoadAsPercentage() const; + + /** Returns the number of over- (or under-) runs recorded since the state was reset. */ + int getXRunCount() const; + + //============================================================================== + /** This class measures the time between its construction and destruction and + adds it to an AudioProcessLoadMeasurer. + + e.g. + @code + { + AudioProcessLoadMeasurer::ScopedTimer timer (myProcessLoadMeasurer); + myCallback->doTheCallback(); + } + @endcode + + @tags{Audio} + */ + struct JUCE_API ScopedTimer + { + ScopedTimer (AudioProcessLoadMeasurer&); + ~ScopedTimer(); + + private: + AudioProcessLoadMeasurer& owner; + double startTime; + + JUCE_DECLARE_NON_COPYABLE (ScopedTimer) + }; + + /** Can be called manually to add the time of a callback to the stats. + Normally you probably would never call this - it's simpler and more robust to + use a ScopedTimer to measure the time using an RAII pattern. + */ + void registerBlockRenderTime (double millisecondsTaken); + +private: + double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0; + int xruns = 0; +}; + + +} // namespace juce diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/JUCE/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 1c2ce7f8b9..f4ab1df073 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/JUCE/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -116,7 +116,7 @@ class AudioBuffer /** Copies another buffer. This buffer will make its own copy of the other's data, unless the buffer was created - using an external data buffer, in which case boths buffers will just point to the same + using an external data buffer, in which case both buffers will just point to the same shared block of data. */ AudioBuffer (const AudioBuffer& other) @@ -172,15 +172,15 @@ class AudioBuffer /** Destructor. This will free any memory allocated by the buffer. */ - ~AudioBuffer() noexcept {} + ~AudioBuffer() = default; /** Move constructor */ AudioBuffer (AudioBuffer&& other) noexcept : numChannels (other.numChannels), size (other.size), allocatedBytes (other.allocatedBytes), - allocatedData (static_cast&&> (other.allocatedData)), - isClear (other.isClear) + allocatedData (std::move (other.allocatedData)), + isClear (other.isClear.load()) { if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) { @@ -205,8 +205,8 @@ class AudioBuffer numChannels = other.numChannels; size = other.size; allocatedBytes = other.allocatedBytes; - allocatedData = static_cast&&> (other.allocatedData); - isClear = other.isClear; + allocatedData = std::move (other.allocatedData); + isClear = other.isClear.load(); if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) { @@ -340,7 +340,7 @@ class AudioBuffer if (newNumSamples != size || newNumChannels != numChannels) { auto allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; - auto channelListSize = ((sizeof (Type*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; + auto channelListSize = ((static_cast (1 + newNumChannels) * sizeof (Type*)) + 15) & ~15u; auto newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (Type)) + channelListSize + 32; @@ -1071,16 +1071,25 @@ class AudioBuffer Type** channels; HeapBlock allocatedData; Type* preallocatedChannelSpace[32]; - bool isClear = false; + std::atomic isClear { false }; void allocateData() { + static_assert (std::alignment_of::value <= std::alignment_of::value, + "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); jassert (size >= 0); - auto channelListSize = sizeof (Type*) * (size_t) (numChannels + 1); + + auto channelListSize = (size_t) (numChannels + 1) * sizeof (Type*); + auto requiredSampleAlignment = std::alignment_of::value; + size_t alignmentOverflow = channelListSize % requiredSampleAlignment; + + if (alignmentOverflow != 0) + channelListSize += requiredSampleAlignment - alignmentOverflow; + allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; allocatedData.malloc (allocatedBytes); channels = reinterpret_cast (allocatedData.get()); - auto* chan = (Type*) (allocatedData + channelListSize); + auto chan = reinterpret_cast (allocatedData + channelListSize); for (int i = 0; i < numChannels; ++i) { diff --git a/JUCE/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/JUCE/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index 4dc44c1b64..a01ad84a24 100644 --- a/JUCE/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/JUCE/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -878,7 +878,7 @@ void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, cons JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, ) #else JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = (float) src[i] * multiplier, - Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))), + Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 (reinterpret_cast (src)))), JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, const Mode::ParallelType mult = Mode::load1 (multiplier);) #endif @@ -1139,6 +1139,7 @@ ScopedNoDenormals::~ScopedNoDenormals() noexcept #endif } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -1146,7 +1147,9 @@ ScopedNoDenormals::~ScopedNoDenormals() noexcept class FloatVectorOperationsTests : public UnitTest { public: - FloatVectorOperationsTests() : UnitTest ("FloatVectorOperations", "Audio") {} + FloatVectorOperationsTests() + : UnitTest ("FloatVectorOperations", UnitTestCategories::audio) + {} template struct TestRunner diff --git a/JUCE/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h b/JUCE/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h deleted file mode 100644 index 80dd6fc09e..0000000000 --- a/JUCE/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h +++ /dev/null @@ -1,215 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Utility class for linearly smoothed values like volume etc. that should - not change abruptly but as a linear ramp, to avoid audio glitches. - - @tags{Audio} -*/ -template -class LinearSmoothedValue -{ -public: - /** Constructor. */ - LinearSmoothedValue() noexcept - { - } - - /** Constructor. */ - LinearSmoothedValue (FloatType initialValue) noexcept - : currentValue (initialValue), target (initialValue) - { - } - - //============================================================================== - /** Reset to a new sample rate and ramp length. - @param sampleRate The sampling rate - @param rampLengthInSeconds The duration of the ramp in seconds - */ - void reset (double sampleRate, double rampLengthInSeconds) noexcept - { - jassert (sampleRate > 0 && rampLengthInSeconds >= 0); - stepsToTarget = (int) std::floor (rampLengthInSeconds * sampleRate); - currentValue = target; - countdown = 0; - } - - //============================================================================== - /** Set a new target value. - - @param newValue The new target value - @param force If true, the value will be set immediately, bypassing the ramp - */ - void setValue (FloatType newValue, bool force = false) noexcept - { - if (force) - { - target = currentValue = newValue; - countdown = 0; - return; - } - - if (target != newValue) - { - target = newValue; - countdown = stepsToTarget; - - if (countdown <= 0) - currentValue = target; - else - step = (target - currentValue) / (FloatType) countdown; - } - } - - //============================================================================== - /** Compute the next value. - @returns Smoothed value - */ - FloatType getNextValue() noexcept - { - if (countdown <= 0) - return target; - - --countdown; - currentValue += step; - return currentValue; - } - - /** Returns true if the current value is currently being interpolated. */ - bool isSmoothing() const noexcept - { - return countdown > 0; - } - - /** Returns the target value towards which the smoothed value is currently moving. */ - FloatType getTargetValue() const noexcept - { - return target; - } - - //============================================================================== - /** Applies a linear smoothed gain to a stream of samples - S[i] *= gain - @param samples Pointer to a raw array of samples - @param numSamples Length of array of samples - */ - void applyGain (FloatType* samples, int numSamples) noexcept - { - jassert(numSamples >= 0); - - if (isSmoothing()) - { - for (int i = 0; i < numSamples; i++) - samples[i] *= getNextValue(); - } - else - { - FloatVectorOperations::multiply (samples, target, numSamples); - } - } - - //============================================================================== - /** Computes output as linear smoothed gain applied to a stream of samples. - Sout[i] = Sin[i] * gain - @param samplesOut A pointer to a raw array of output samples - @param samplesIn A pointer to a raw array of input samples - @param numSamples The length of the array of samples - */ - void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept - { - jassert (numSamples >= 0); - - if (isSmoothing()) - { - for (int i = 0; i < numSamples; i++) - samplesOut[i] = samplesIn[i] * getNextValue(); - } - else - { - FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples); - } - } - - //============================================================================== - /** Applies a linear smoothed gain to a buffer */ - void applyGain (AudioBuffer& buffer, int numSamples) noexcept - { - jassert (numSamples >= 0); - - if (isSmoothing()) - { - if (buffer.getNumChannels() == 1) - { - FloatType* samples = buffer.getWritePointer(0); - - for (int i = 0; i < numSamples; i++) - samples[i] *= getNextValue(); - } - else - { - for (int i = 0; i < numSamples; i++) - { - const FloatType gain = getNextValue(); - - for (int channel = 0; channel < buffer.getNumChannels(); channel++) - buffer.setSample (channel, i, buffer.getSample (channel, i) * gain); - } - } - } - else - { - buffer.applyGain (0, numSamples, target); - } - } - - //============================================================================== - /** Skip the next numSamples samples. - - This is identical to calling getNextValue numSamples times. - @see getNextValue - */ - void skip (int numSamples) noexcept - { - if (numSamples >= countdown) - { - currentValue = target; - countdown = 0; - } - else - { - currentValue += (step * static_cast (numSamples)); - countdown -= numSamples; - } - } - -private: - //============================================================================== - FloatType currentValue = 0, target = 0, step = 0; - int countdown = 0, stepsToTarget = 0; -}; - -} // namespace juce diff --git a/JUCE/modules/juce_audio_basics/juce_audio_basics.cpp b/JUCE/modules/juce_audio_basics/juce_audio_basics.cpp index 56371d0d10..c142c6d6c1 100644 --- a/JUCE/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/JUCE/modules/juce_audio_basics/juce_audio_basics.cpp @@ -56,9 +56,11 @@ #include "buffers/juce_AudioDataConverters.cpp" #include "buffers/juce_FloatVectorOperations.cpp" #include "buffers/juce_AudioChannelSet.cpp" -#include "effects/juce_IIRFilter.cpp" -#include "effects/juce_LagrangeInterpolator.cpp" -#include "effects/juce_CatmullRomInterpolator.cpp" +#include "buffers/juce_AudioProcessLoadMeasurer.cpp" +#include "utilities/juce_IIRFilter.cpp" +#include "utilities/juce_LagrangeInterpolator.cpp" +#include "utilities/juce_CatmullRomInterpolator.cpp" +#include "utilities/juce_SmoothedValue.cpp" #include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiFile.cpp" #include "midi/juce_MidiKeyboardState.cpp" diff --git a/JUCE/modules/juce_audio_basics/juce_audio_basics.h b/JUCE/modules/juce_audio_basics/juce_audio_basics.h index 3662bc3604..93fd4c6510 100644 --- a/JUCE/modules/juce_audio_basics/juce_audio_basics.h +++ b/JUCE/modules/juce_audio_basics/juce_audio_basics.h @@ -20,6 +20,7 @@ ============================================================================== */ + /******************************************************************************* The block below describes the properties of this module, and is read by the Projucer to automatically generate project code that uses it. @@ -29,17 +30,17 @@ BEGIN_JUCE_MODULE_DECLARATION - ID: juce_audio_basics - vendor: juce - version: 5.3.2 - name: JUCE audio and MIDI data classes - description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. - website: http://www.juce.com/juce - license: ISC + ID: juce_audio_basics + vendor: juce + version: 5.4.7 + name: JUCE audio and MIDI data classes + description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. + website: http://www.juce.com/juce + license: ISC - dependencies: juce_core - OSXFrameworks: Accelerate - iOSFrameworks: Accelerate + dependencies: juce_core + OSXFrameworks: Accelerate + iOSFrameworks: Accelerate END_JUCE_MODULE_DECLARATION @@ -84,12 +85,14 @@ #include "buffers/juce_FloatVectorOperations.h" #include "buffers/juce_AudioSampleBuffer.h" #include "buffers/juce_AudioChannelSet.h" -#include "effects/juce_Decibels.h" -#include "effects/juce_IIRFilter.h" -#include "effects/juce_LagrangeInterpolator.h" -#include "effects/juce_CatmullRomInterpolator.h" -#include "effects/juce_LinearSmoothedValue.h" -#include "effects/juce_Reverb.h" +#include "buffers/juce_AudioProcessLoadMeasurer.h" +#include "utilities/juce_Decibels.h" +#include "utilities/juce_IIRFilter.h" +#include "utilities/juce_LagrangeInterpolator.h" +#include "utilities/juce_CatmullRomInterpolator.h" +#include "utilities/juce_SmoothedValue.h" +#include "utilities/juce_Reverb.h" +#include "utilities/juce_ADSR.h" #include "midi/juce_MidiMessage.h" #include "midi/juce_MidiBuffer.h" #include "midi/juce_MidiMessageSequence.h" diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp b/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp index 91689d332c..0501698bf6 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp @@ -25,51 +25,53 @@ namespace juce namespace MidiBufferHelpers { - inline int getEventTime (const void* const d) noexcept + inline int getEventTime (const void* d) noexcept { return readUnaligned (d); } - inline uint16 getEventDataSize (const void* const d) noexcept + inline uint16 getEventDataSize (const void* d) noexcept { return readUnaligned (static_cast (d) + sizeof (int32)); } - inline uint16 getEventTotalSize (const void* const d) noexcept + inline uint16 getEventTotalSize (const void* d) noexcept { return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); } - static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept + static int findActualEventLength (const uint8* data, int maxBytes) noexcept { - unsigned int byte = (unsigned int) *data; - int size = 0; + auto byte = (unsigned int) *data; if (byte == 0xf0 || byte == 0xf7) { - const uint8* d = data + 1; + int i = 1; - while (d < data + maxBytes) - if (*d++ == 0xf7) + while (i < maxBytes) + if (data[i++] == 0xf7) break; - size = (int) (d - data); + return i; } - else if (byte == 0xff) + + if (byte == 0xff) { + if (maxBytes == 1) + return 1; + 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)); + auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); + return jmin (maxBytes, n + 2 + bytesLeft); } - return size; + if (byte >= 0x80) + return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); + + return 0; } - static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept + static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept { while (d < endData && getEventTime (d) <= samplePosition) d += getEventTotalSize (d); @@ -100,41 +102,41 @@ void MidiBuffer::clear() noexcept { data.clearQuick(); void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } -void MidiBuffer::clear (const int startSample, const int numSamples) +void MidiBuffer::clear (int startSample, int numSamples) { - uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); - uint8* const end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); + auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); + auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); } -void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) +void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) { addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); } -void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) +void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) { - const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); + auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); if (numBytes > 0) { - const size_t newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); - const int offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); + auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); + auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); data.insertMultiple (offset, 0, (int) newItemSize); - uint8* const d = data.begin() + offset; + auto* d = data.begin() + offset; writeUnaligned (d, sampleNumber); - writeUnaligned (d + 4, static_cast (numBytes)); - memcpy (d + 6, newData, (size_t) numBytes); + d += sizeof (int32); + writeUnaligned (d, static_cast (numBytes)); + d += sizeof (uint16); + memcpy (d, newData, (size_t) numBytes); } } void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, - const int startSample, - const int numSamples, - const int sampleDeltaToAdd) + int startSample, int numSamples, int sampleDeltaToAdd) { Iterator i (otherBuffer); i.setNextSamplePosition (startSample); @@ -152,9 +154,9 @@ void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, int MidiBuffer::getNumEvents() const noexcept { int n = 0; - const uint8* const end = data.end(); + auto end = data.end(); - for (const uint8* d = data.begin(); d < end; ++n) + for (auto d = data.begin(); d < end; ++n) d += MidiBufferHelpers::getEventTotalSize (d); return n; @@ -170,11 +172,11 @@ int MidiBuffer::getLastEventTime() const noexcept if (data.size() == 0) return 0; - const uint8* const endData = data.end(); + auto endData = data.end(); - for (const uint8* d = data.begin();;) + for (auto d = data.begin();;) { - const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); + auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d); if (nextOne >= endData) return MidiBufferHelpers::getEventTime (d); @@ -189,24 +191,24 @@ MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept { } -MidiBuffer::Iterator::~Iterator() noexcept{} +MidiBuffer::Iterator::~Iterator() noexcept {} -void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept +void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept { data = buffer.data.begin(); - const uint8* const dataEnd = buffer.data.end(); + auto dataEnd = buffer.data.end(); while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) data += MidiBufferHelpers::getEventTotalSize (data); } -bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept +bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept { if (data >= buffer.data.end()) return false; samplePosition = MidiBufferHelpers::getEventTime (data); - const int itemSize = MidiBufferHelpers::getEventDataSize (data); + auto itemSize = MidiBufferHelpers::getEventDataSize (data); numBytes = itemSize; midiData = data + sizeof (int32) + sizeof (uint16); data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; @@ -220,7 +222,7 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio return false; samplePosition = MidiBufferHelpers::getEventTime (data); - const int itemSize = MidiBufferHelpers::getEventDataSize (data); + auto itemSize = MidiBufferHelpers::getEventDataSize (data); result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.h index 27e86c7cc0..8344a5ed84 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiBuffer.h @@ -177,9 +177,6 @@ class JUCE_API MidiBuffer /** Creates a copy of an iterator. */ Iterator (const Iterator&) = default; - // VS2013 requires this, even if it's unused. - Iterator& operator= (const Iterator&) = delete; - /** Destructor. */ ~Iterator() noexcept; diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 65e7b0edd2..58cbf7c90a 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -169,14 +169,14 @@ MidiFile& MidiFile::operator= (const MidiFile& other) } MidiFile::MidiFile (MidiFile&& other) - : tracks (static_cast&&> (other.tracks)), + : tracks (std::move (other.tracks)), timeFormat (other.timeFormat) { } MidiFile& MidiFile::operator= (MidiFile&& other) { - tracks = static_cast&&> (other.tracks); + tracks = std::move (other.tracks); timeFormat = other.timeFormat; return *this; } @@ -245,7 +245,7 @@ double MidiFile::getLastTimestamp() const } //============================================================================== -bool MidiFile::readFrom (InputStream& sourceStream) +bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) { clear(); MemoryBlock data; @@ -262,25 +262,26 @@ bool MidiFile::readFrom (InputStream& sourceStream) if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) { size -= (size_t) (d - static_cast (data.getData())); - int track = 0; - while (size > 0 && track < expectedTracks) + for (;;) { auto chunkType = (int) ByteOrder::bigEndianInt (d); d += 4; auto chunkSize = (int) ByteOrder::bigEndianInt (d); d += 4; - if (chunkSize <= 0) + if (chunkSize <= 0 || (size_t) chunkSize > size) break; if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) - readNextTrack (d, chunkSize); + readNextTrack (d, chunkSize, createMatchingNoteOffs); + + if (++track >= expectedTracks) + break; size -= (size_t) chunkSize + 8; d += chunkSize; - ++track; } return true; @@ -290,7 +291,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) return false; } -void MidiFile::readNextTrack (const uint8* data, int size) +void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) { double time = 0; uint8 lastStatusByte = 0; @@ -337,7 +338,9 @@ void MidiFile::readNextTrack (const uint8* data, int size) }); addTrack (result); - tracks.getLast()->updateMatchedPairs(); + + if (createMatchingNoteOffs) + tracks.getLast()->updateMatchedPairs(); } //============================================================================== @@ -361,7 +364,7 @@ void MidiFile::convertTimestampTicksToSeconds() } //============================================================================== -bool MidiFile::writeTo (OutputStream& out, int midiFileType) +bool MidiFile::writeTo (OutputStream& out, int midiFileType) const { jassert (midiFileType >= 0 && midiFileType <= 2); @@ -379,7 +382,7 @@ bool MidiFile::writeTo (OutputStream& out, int midiFileType) return true; } -bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) +bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const { MemoryOutputStream out; diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.h index 2226b320ef..0f3f9f00d1 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiFile.h @@ -156,16 +156,25 @@ class JUCE_API MidiFile terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() method. + @param sourceStream the source stream + @param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will + be automatically added at the end of the file by calling + MidiMessageSequence::updateMatchedPairs on each track. + @returns true if the stream was read successfully */ - bool readFrom (InputStream& sourceStream); + bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true); /** Writes the midi tracks as a standard midi file. The midiFileType value is written as the file's format type, which can be 0, 1 or 2 - see the midi file spec for more info about that. + + @param destStream the destination stream + @param midiFileType the type of midi file + @returns true if the operation succeeded. */ - bool writeTo (OutputStream& destStream, int midiFileType = 1); + bool writeTo (OutputStream& destStream, int midiFileType = 1) const; /** Converts the timestamp of all the midi events from midi ticks to seconds. @@ -174,14 +183,13 @@ class JUCE_API MidiFile */ void convertTimestampTicksToSeconds(); - private: //============================================================================== OwnedArray tracks; short timeFormat; - void readNextTrack (const uint8*, int size); - bool writeTrack (OutputStream&, const MidiMessageSequence&); + void readNextTrack (const uint8*, int, bool); + bool writeTrack (OutputStream&, const MidiMessageSequence&) const; JUCE_LEAK_DETECTOR (MidiFile) }; diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h index ddbbe2dba2..47aaf6923c 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h @@ -38,8 +38,8 @@ class JUCE_API MidiKeyboardStateListener { public: //============================================================================== - MidiKeyboardStateListener() noexcept {} - virtual ~MidiKeyboardStateListener() {} + MidiKeyboardStateListener() = default; + virtual ~MidiKeyboardStateListener() = default; //============================================================================== /** Called when one of the MidiKeyboardState's keys is pressed. @@ -73,7 +73,7 @@ class JUCE_API MidiKeyboardStateListener Represents a piano keyboard, keeping track of which keys are currently pressed. This object can parse a stream of midi events, using them to update its idea - of which keys are pressed for each individiual midi channel. + of which keys are pressed for each individual midi channel. When keys go up or down, it can broadcast these events to listener objects. @@ -135,7 +135,7 @@ class JUCE_API MidiKeyboardState It will also trigger a synchronous callback to the listeners to tell them that the key has gone up. - But if the note isn't acutally down for the given channel, this method will in fact do nothing. + But if the note isn't actually down for the given channel, this method will in fact do nothing. */ void noteOff (int midiChannel, int midiNoteNumber, float velocity); diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index d1ad950583..9ccccfacd8 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -224,9 +224,16 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const } else if (byte == 0xff) { - int n; - const int bytesLeft = readVariableLengthVal (src + 1, n); - size = jmin (sz + 1, n + 2 + bytesLeft); + if (sz == 1) + { + size = 1; + } + else + { + int n; + const int bytesLeft = readVariableLengthVal (src + 1, n); + size = jmin (sz + 1, n + 2 + bytesLeft); + } auto dest = allocateSpace (size); *dest = (uint8) byte; diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.h index bc7669622b..5628c3ae92 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -83,19 +83,19 @@ class JUCE_API MidiMessage complete message, and will return the number of bytes it used. This lets you read a sequence of midi messages from a file or stream. - @param data the data to read from - @param maxBytesToUse the maximum number of bytes it's allowed to read - @param numBytesUsed returns the number of bytes that were actually needed - @param lastStatusByte in a sequence of midi messages, the initial byte - can be dropped from a message if it's the same as the - first byte of the previous message, so this lets you - supply the byte to use if the first byte of the message - has in fact been dropped. - @param timeStamp the time to give the midi message - this value doesn't - use any particular units, so will be application-specific + @param data the data to read from + @param maxBytesToUse the maximum number of bytes it's allowed to read + @param numBytesUsed returns the number of bytes that were actually needed + @param lastStatusByte in a sequence of midi messages, the initial byte + can be dropped from a message if it's the same as the + first byte of the previous message, so this lets you + supply the byte to use if the first byte of the message + has in fact been dropped. + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether - to expect the data to begin with a variable-length field - indicating its size + to expect the data to begin with a variable-length + field indicating its size */ MidiMessage (const void* data, int maxBytesToUse, int& numBytesUsed, uint8 lastStatusByte, diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index 0321e46674..d74aa583dd 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -24,7 +24,7 @@ namespace juce { MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} -MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (static_cast (mm)) {} +MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {} MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {} //============================================================================== @@ -53,13 +53,13 @@ MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& } MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept - : list (static_cast&&> (other.list)) + : list (std::move (other.list)) { } MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept { - list = static_cast&&> (other.list); + list = std::move (other.list); return *this; } @@ -87,8 +87,10 @@ MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int return list[index]; } -MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() const noexcept { return list.begin(); } -MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() const noexcept { return list.end(); } +MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); } +MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); } +MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); } +MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); } double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept { @@ -174,7 +176,7 @@ MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiM MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment) { - return addEvent (new MidiEventHolder (static_cast (newMessage)), timeAdjustment); + return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment); } void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) @@ -344,11 +346,16 @@ void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, dou } } + +//============================================================================== +//============================================================================== #if JUCE_UNIT_TESTS -struct MidiMessageSequenceTest : public juce::UnitTest +struct MidiMessageSequenceTest : public UnitTest { - MidiMessageSequenceTest() : juce::UnitTest ("MidiMessageSequence") {} + MidiMessageSequenceTest() + : UnitTest ("MidiMessageSequence", UnitTestCategories::midi) + {} void runTest() override { @@ -371,7 +378,7 @@ struct MidiMessageSequenceTest : public juce::UnitTest expectEquals (s.getIndexOfMatchingKeyUp (0), 2); expectEquals (s.getIndexOfMatchingKeyUp (1), 3); - beginTest ("Time & indeces"); + beginTest ("Time & indices"); expectEquals (s.getNextIndexAtTime (0.5), 1); expectEquals (s.getNextIndexAtTime (2.5), 2); expectEquals (s.getNextIndexAtTime (9.0), 4); diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h index d4f4f9265c..5d4187ea38 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h @@ -103,10 +103,16 @@ class JUCE_API MidiMessageSequence MidiEventHolder* getEventPointer (int index) const noexcept; /** Iterator for the list of MidiEventHolders */ - MidiEventHolder** begin() const noexcept; + MidiEventHolder** begin() noexcept; /** Iterator for the list of MidiEventHolders */ - MidiEventHolder** end() const noexcept; + MidiEventHolder* const* begin() const noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder** end() noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder* const* end() const noexcept; /** Returns the time of the note-up that matches the note-on at this index. If the event at this index isn't a note-on, it'll just return 0. diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index 9e2c2df329..10e8e03a4c 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -159,6 +159,7 @@ MidiBuffer MidiRPNGenerator::generate (int midiChannel, return buffer; } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -166,7 +167,9 @@ MidiBuffer MidiRPNGenerator::generate (int midiChannel, class MidiRPNDetectorTests : public UnitTest { public: - MidiRPNDetectorTests() : UnitTest ("MidiRPNDetector class", "MIDI/MPE") {} + MidiRPNDetectorTests() + : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi) + {} void runTest() override { @@ -308,7 +311,9 @@ static MidiRPNDetectorTests MidiRPNDetectorUnitTests; class MidiRPNGeneratorTests : public UnitTest { public: - MidiRPNGeneratorTests() : UnitTest ("MidiRPNGenerator class", "MIDI/MPE") {} + MidiRPNGeneratorTests() + : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi) + {} void runTest() override { @@ -371,6 +376,6 @@ class MidiRPNGeneratorTests : public UnitTest static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.h b/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.h index 94ffc731e3..869afac782 100644 --- a/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.h +++ b/JUCE/modules/juce_audio_basics/midi/juce_MidiRPN.h @@ -142,7 +142,7 @@ class JUCE_API MidiRPNGenerator @param use14BitValue If true (default), the value will have 14-bit precision (two MIDI bytes). If false, instead the value will have - 7-bit presision (a single MIDI byte). + 7-bit precision (a single MIDI byte). */ static MidiBuffer generate (int channel, int parameterNumber, diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index b2f2cb6a6d..df2e4660f3 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp @@ -129,12 +129,12 @@ void MPEInstrument::setTimbreTrackingMode (TrackingMode modeToUse) } //============================================================================== -void MPEInstrument::addListener (Listener* const listenerToAdd) noexcept +void MPEInstrument::addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); } -void MPEInstrument::removeListener (Listener* const listenerToRemove) noexcept +void MPEInstrument::removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); } @@ -151,12 +151,13 @@ void MPEInstrument::processNextMidiEvent (const MidiMessage& message) else if (message.isPitchWheel()) processMidiPitchWheelMessage (message); else if (message.isChannelPressure()) processMidiChannelPressureMessage (message); else if (message.isController()) processMidiControllerMessage (message); + else if (message.isAftertouch()) processMidiAfterTouchMessage (message); } //============================================================================== void MPEInstrument::processMidiNoteOnMessage (const MidiMessage& message) { - // Note: if a note-on with velocity = 0 is used to convey a note-off, + // Note: If a note-on with velocity = 0 is used to convey a note-off, // then the actual note-off velocity is not known. In this case, // the MPE convention is to use note-off velocity = 64. @@ -241,7 +242,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me { auto& note = notes.getReference (i); - if (zone.isUsingChannelAsMemberChannel (note.midiChannel)) + if (zone.isUsing (note.midiChannel)) { note.keyState = MPENote::off; note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number @@ -252,6 +253,15 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me } } +void MPEInstrument::processMidiAfterTouchMessage (const MidiMessage& message) +{ + if (! isMasterChannel (message.getChannel())) + return; + + polyAftertouch (message.getChannel(), message.getNoteNumber(), + MPEValue::from7BitInt (message.getAfterTouchValue())); +} + //============================================================================== void MPEInstrument::handlePressureMSB (int midiChannel, int value) noexcept { @@ -284,7 +294,7 @@ void MPEInstrument::noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity) { - if (! isMemberChannel (midiChannel)) + if (! isUsingChannel (midiChannel)) return; MPENote newNote (midiChannel, @@ -316,7 +326,7 @@ void MPEInstrument::noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity) { - if (notes.isEmpty() || ! isMemberChannel (midiChannel)) + if (notes.isEmpty() || ! isUsingChannel (midiChannel)) return; const ScopedLock sl (lock); @@ -326,11 +336,13 @@ void MPEInstrument::noteOff (int midiChannel, note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; note->noteOffVelocity = midiNoteOffVelocity; - // last dimension values received for this note should not be re-used for - // any new notes, so reset them: - pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); - pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); - timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + // If no more notes are playing on this channel, reset the dimension values + if (getLastNotePlayedPtr (midiChannel) == nullptr) + { + pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); + pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + } if (note->keyState == MPENote::off) { @@ -363,6 +375,24 @@ void MPEInstrument::timbre (int midiChannel, MPEValue value) updateDimension (midiChannel, timbreDimension, value); } +void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value) +{ + const ScopedLock sl (lock); + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel + && note.initialNote == midiNoteNumber + && pressureDimension.getValue (note) != value) + { + pressureDimension.getValue (note) = value; + callListenersDimensionChanged (note, pressureDimension); + } + } +} + MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const { if (getLastNotePlayedPtr (midiChannel) != nullptr) @@ -416,7 +446,7 @@ void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimen { auto& note = notes.getReference (i); - if (! zone.isUsingChannelAsMemberChannel (note.midiChannel)) + if (! zone.isUsing (note.midiChannel)) continue; if (&dimension == &pitchbendDimension) @@ -467,9 +497,9 @@ void MPEInstrument::updateNoteTotalPitchbend (MPENote& note) { auto zone = zoneLayout.getLowerZone(); - if (! zone.isUsingChannelAsMemberChannel (note.midiChannel)) + if (! zone.isUsing (note.midiChannel)) { - if (zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (note.midiChannel)) + if (zoneLayout.getUpperZone().isUsing (note.midiChannel)) { zone = zoneLayout.getUpperZone(); } @@ -481,7 +511,10 @@ void MPEInstrument::updateNoteTotalPitchbend (MPENote& note) } } - auto notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange; + auto notePitchbendInSemitones = 0.0f; + + if (zone.isUsingChannelAsMemberChannel (note.midiChannel)) + notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange; auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1] .asSignedFloat() @@ -520,7 +553,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool { auto& note = notes.getReference (i); - if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsingChannelAsMemberChannel (note.midiChannel)) + if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsing (note.midiChannel)) { if (note.keyState == MPENote::keyDown && isDown) note.keyState = MPENote::keyDownAndSustained; @@ -560,7 +593,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool } //============================================================================== -bool MPEInstrument::isMemberChannel (int midiChannel) noexcept +bool MPEInstrument::isMemberChannel (int midiChannel) const noexcept { if (legacyMode.isEnabled) return legacyMode.channelRange.contains (midiChannel); @@ -574,8 +607,22 @@ bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept if (legacyMode.isEnabled) return false; - return (midiChannel == 1 || midiChannel == 16); + const auto lowerZone = zoneLayout.getLowerZone(); + const auto upperZone = zoneLayout.getUpperZone(); + + return (lowerZone.isActive() && midiChannel == lowerZone.getMasterChannel()) + || (upperZone.isActive() && midiChannel == upperZone.getMasterChannel()); +} + +bool MPEInstrument::isUsingChannel (int midiChannel) const noexcept +{ + if (legacyMode.isEnabled) + return legacyMode.channelRange.contains (midiChannel); + + return zoneLayout.getLowerZone().isUsing (midiChannel) + || zoneLayout.getUpperZone().isUsing (midiChannel); } + //============================================================================== int MPEInstrument::getNumPlayingNotes() const noexcept { @@ -679,7 +726,7 @@ MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) noexcept const MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) const noexcept { int initialNoteMax = -1; - MPENote* result = nullptr; + const MPENote* result = nullptr; for (auto i = notes.size(); --i >= 0;) { @@ -705,7 +752,7 @@ MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) noexcept const MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) const noexcept { int initialNoteMin = 128; - MPENote* result = nullptr; + const MPENote* result = nullptr; for (auto i = notes.size(); --i >= 0;) { @@ -744,6 +791,7 @@ void MPEInstrument::releaseAllNotes() notes.clear(); } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -752,7 +800,7 @@ class MPEInstrumentTests : public UnitTest { public: MPEInstrumentTests() - : UnitTest ("MPEInstrument class", "MIDI/MPE") + : UnitTest ("MPEInstrument class", UnitTestCategories::midi) { // using lower and upper MPE zones with the following layout for testing // @@ -798,12 +846,7 @@ class MPEInstrumentTests : public UnitTest UnitTestInstrument test; test.setZoneLayout (testLayout); - // note-on on master channel - ignore - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteAddedCallCounter, 0); - - // note-on on any other channel - ignore + // note-on on unused channel - ignore test.noteOn (7, 60, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 0); expectEquals (test.noteAddedCallCounter, 0); @@ -819,7 +862,21 @@ class MPEInstrumentTests : public UnitTest expectEquals (test.getNumPlayingNotes(), 0); expectEquals (test.noteReleasedCallCounter, 1); expectHasFinishedNote (test, 3, 60, 33); + + + // note-on on master channel - create new note + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 1); + expectEquals (test.noteAddedCallCounter, 2); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); + + // note-off + test.noteOff (1, 62, MPEValue::from7BitInt (33)); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 2); + expectHasFinishedNote (test, 1, 62, 33); } + { UnitTestInstrument test; test.setZoneLayout (testLayout); @@ -837,6 +894,7 @@ class MPEInstrumentTests : public UnitTest expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteReleasedCallCounter, 0); } + { // can have multiple notes on the same channel UnitTestInstrument test; @@ -1187,6 +1245,22 @@ class MPEInstrumentTests : public UnitTest expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); } + + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // master channel will use poly-aftertouch for pressure + test.noteOn (16, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); + test.aftertouch (16, 60, MPEValue::from7BitInt (27)); + expectNote (test.getNote (16, 60), 100, 27, 8192, 64, MPENote::keyDown); + + // member channels will not respond to poly-aftertouch + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.aftertouch (3, 60, MPEValue::from7BitInt (50)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } } beginTest ("pitchbend"); @@ -2142,6 +2216,12 @@ class MPEInstrumentTests : public UnitTest lastSostenutoPedalValueReceived = value; } + void aftertouch (int midiChannel, int midiNoteNumber, MPEValue value) + { + const auto message = juce::MidiMessage::aftertouchChange (midiChannel, midiNoteNumber, value.as7BitInt()); + processNextMidiEvent (message); + } + int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter, pressureCallCounter, timbreCallCounter, sustainPedalCallCounter, sostenutoPedalCallCounter, noteAddedCallCounter, @@ -2207,6 +2287,6 @@ class MPEInstrumentTests : public UnitTest static MPEInstrumentTests MPEInstrumentUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index 4582065df3..91a0b2b3dc 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -27,7 +27,7 @@ namespace juce /** This class represents an instrument handling MPE. - It has an MPE zone layout and maintans a state of currently + It has an MPE zone layout and maintains a state of currently active (playing) notes and the values of their dimensions of expression. You can trigger and modulate notes: @@ -90,7 +90,7 @@ class JUCE_API MPEInstrument When in legacy mode, this will return true if the given channel is contained in the current legacy mode channel range; false otherwise. */ - bool isMemberChannel (int midiChannel) noexcept; + bool isMemberChannel (int midiChannel) const noexcept; /** Returns true if the given MIDI channel (1-16) is a master channel (channel 1 or 16). @@ -99,6 +99,14 @@ class JUCE_API MPEInstrument */ bool isMasterChannel (int midiChannel) const noexcept; + /** Returns true if the given MIDI channel (1-16) is used by any of the + MPEInstrument's MPE zones; false otherwise. + + When in legacy mode, this will return true if the given channel is + contained in the current legacy mode channel range; false otherwise. + */ + bool isUsingChannel (int midiChannel) const noexcept; + //============================================================================== /** The MPE note tracking mode. In case there is more than one note playing simultaneously on the same MIDI channel, this determines which of these @@ -177,6 +185,13 @@ class JUCE_API MPEInstrument */ virtual void timbre (int midiChannel, MPEValue value); + /** Request a poly-aftertouch change for a given note number. + + The change will be broadcast to all notes sharing the channel and note + number of the change message. + */ + virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value); + /** Request a sustain pedal press or release. If midiChannel is a zone's master channel, this will act on all notes in @@ -241,17 +256,17 @@ class JUCE_API MPEInstrument { public: /** Destructor. */ - virtual ~Listener() {} + virtual ~Listener() = default; /** Implement this callback to be informed whenever a new expressive MIDI note is triggered. */ - virtual void noteAdded (MPENote newNote) = 0; + virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); } /** Implement this callback to be informed whenever a currently playing MPE note's pressure value changes. */ - virtual void notePressureChanged (MPENote changedNote) = 0; + virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); } /** Implement this callback to be informed whenever a currently playing MPE note's pitchbend value changes. @@ -260,36 +275,36 @@ class JUCE_API MPEInstrument master channel pitchbend event, or if both occur simultaneously. Call MPENote::getFrequencyInHertz to get the effective note frequency. */ - virtual void notePitchbendChanged (MPENote changedNote) = 0; + virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); } /** Implement this callback to be informed whenever a currently playing MPE note's timbre value changes. */ - virtual void noteTimbreChanged (MPENote changedNote) = 0; + virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); } /** Implement this callback to be informed whether a currently playing MPE note's key state (whether the key is down and/or the note is sustained) has changed. - Note: if the key state changes to MPENote::off, noteReleased is + Note: If the key state changes to MPENote::off, noteReleased is called instead. */ - virtual void noteKeyStateChanged (MPENote changedNote) = 0; + virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); } /** Implement this callback to be informed whenever an MPE note is released (either by a note-off message, or by a sustain/sostenuto pedal release for a note that already received a note-off), and should therefore stop playing. */ - virtual void noteReleased (MPENote finishedNote) = 0; + virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } }; //============================================================================== /** Adds a listener. */ - void addListener (Listener* listenerToAdd) noexcept; + void addListener (Listener* listenerToAdd); /** Removes a listener. */ - void removeListener (Listener* listenerToRemove) noexcept; + void removeListener (Listener* listenerToRemove); //============================================================================== /** Puts the instrument into legacy mode. @@ -352,8 +367,7 @@ class JUCE_API MPEInstrument struct MPEDimension { - MPEDimension() noexcept : trackingMode (lastNotePlayedOnChannel) {} - TrackingMode trackingMode; + TrackingMode trackingMode = lastNotePlayedOnChannel; MPEValue lastValueReceivedOnChannel[16]; MPEValue MPENote::* value; MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } @@ -374,6 +388,7 @@ class JUCE_API MPEInstrument void processMidiChannelPressureMessage (const MidiMessage&); void processMidiControllerMessage (const MidiMessage&); void processMidiResetAllControllersMessage (const MidiMessage&); + void processMidiAfterTouchMessage (const MidiMessage&); void handlePressureMSB (int midiChannel, int value) noexcept; void handlePressureLSB (int midiChannel, int value) noexcept; void handleTimbreMSB (int midiChannel, int value) noexcept; diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp index f694944285..214df7cfde 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp @@ -106,6 +106,7 @@ MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout) return buffer; } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -113,7 +114,9 @@ MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout) class MPEMessagesTests : public UnitTest { public: - MPEMessagesTests() : UnitTest ("MPEMessages class", "MIDI/MPE") {} + MPEMessagesTests() + : UnitTest ("MPEMessages class", UnitTestCategories::midi) + {} void runTest() override { @@ -215,7 +218,7 @@ class MPEMessagesTests : public UnitTest std::size_t pos = 0; MidiBuffer::Iterator iter (midiBuffer); MidiMessage midiMessage; - int samplePosition; // Note: not actually used, so no need to initialise. + int samplePosition; // Note: Not actually used, so no need to initialise. while (iter.getNextEvent (midiMessage, samplePosition)) { @@ -233,6 +236,6 @@ class MPEMessagesTests : public UnitTest static MPEMessagesTests MPEMessagesUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.h index 133ce3d87b..1275dacd39 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEMessages.h @@ -35,7 +35,7 @@ namespace juce class instead. You just need to take care to send them to the appropriate per-note MIDI channel. - Note: if you are working with an MPEZoneLayout object inside your app, + Note: If you are working with an MPEZoneLayout object inside your app, you should not use the message sequences provided here. Instead, you should change the zone layout programmatically with the member functions provided in the MPEZoneLayout class itself. You should also make sure that the Expressive @@ -99,7 +99,7 @@ class JUCE_API MPEMessages /** Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will reset the whole MPE zone layout of the - device to the laoyut passed in. This will first clear the current lower and upper + device to the layout passed in. This will first clear the current lower and upper zones, then then set the zones contained in the passed-in zone layout, and set their per-note and master pitchbend ranges to their current values. */ diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.cpp index c4c5707cd8..742c7786af 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.cpp @@ -84,6 +84,7 @@ bool MPENote::operator!= (const MPENote& other) const noexcept return noteID != other.noteID; } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -91,7 +92,9 @@ bool MPENote::operator!= (const MPENote& other) const noexcept class MPENoteTests : public UnitTest { public: - MPENoteTests() : UnitTest ("MPENote class", "MIDI/MPE") {} + MPENoteTests() + : UnitTest ("MPENote class", UnitTestCategories::midi) + {} //============================================================================== void runTest() override @@ -119,6 +122,6 @@ class MPENoteTests : public UnitTest static MPENoteTests MPENoteUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.h index 33d992b48e..0a99745188 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPENote.h @@ -132,7 +132,7 @@ struct JUCE_API MPENote */ MPEValue pressure { MPEValue::centreValue() }; - /** Inital value of timbre when the note was triggered. + /** Initial value of timbre when the note was triggered. This should never change during the lifetime of an MPENote object. */ MPEValue initialTimbre { MPEValue::centreValue() }; diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp index ea0028e477..8eaf95ce7d 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp @@ -42,13 +42,16 @@ MPESynthesiser::~MPESynthesiser() void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) { jassert (voice != nullptr); + voice->currentlyPlayingNote = noteToStart; + voice->noteOnTime = lastNoteOnCounter++; voice->noteStarted(); } void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) { jassert (voice != nullptr); + voice->currentlyPlayingNote = noteToStop; voice->noteStopped (allowTailOff); } @@ -197,7 +200,7 @@ MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceF // compilers generating code containing heap allocations.. struct Sorter { - bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); } + bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; } }; std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); @@ -296,10 +299,14 @@ void MPESynthesiser::reduceNumVoices (const int newNumVoices) void MPESynthesiser::turnOffAllVoices (bool allowTailOff) { - // first turn off all voices (it's more efficient to do this immediately - // rather than to go through the MPEInstrument for this). - for (auto* voice : voices) - voice->noteStopped (allowTailOff); + { + const ScopedLock sl (voicesLock); + + // first turn off all voices (it's more efficient to do this immediately + // rather than to go through the MPEInstrument for this). + for (auto* voice : voices) + voice->noteStopped (allowTailOff); + } // finally make sure the MPE Instrument also doesn't have any notes anymore. instrument->releaseAllNotes(); @@ -308,6 +315,8 @@ void MPESynthesiser::turnOffAllVoices (bool allowTailOff) //============================================================================== void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) { + const ScopedLock sl (voicesLock); + for (auto* voice : voices) { if (voice->isActive()) @@ -317,6 +326,8 @@ void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSa void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) { + const ScopedLock sl (voicesLock); + for (auto* voice : voices) { if (voice->isActive()) diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h index d30269375f..8a999d66e8 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h @@ -69,10 +69,10 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase @see MPESynthesiserBase, MPEInstrument */ - MPESynthesiser (MPEInstrument* instrument); + MPESynthesiser (MPEInstrument* instrumentToUse); /** Destructor. */ - ~MPESynthesiser(); + ~MPESynthesiser() override; //============================================================================== /** Deletes all voices. */ @@ -188,7 +188,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state will become inconsistent. */ - virtual void noteAdded (MPENote newNote) override; + void noteAdded (MPENote newNote) override; /** Stops playing a note. @@ -203,7 +203,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state will become inconsistent. */ - virtual void noteReleased (MPENote finishedNote) override; + void noteReleased (MPENote finishedNote) override; /** Will find any voice that is currently playing changedNote, update its currently playing note, and call its notePressureChanged method. @@ -211,7 +211,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase This method will be called automatically according to the midi data passed into renderNextBlock(). Do not call it yourself. */ - virtual void notePressureChanged (MPENote changedNote) override; + void notePressureChanged (MPENote changedNote) override; /** Will find any voice that is currently playing changedNote, update its currently playing note, and call its notePitchbendChanged method. @@ -219,7 +219,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase This method will be called automatically according to the midi data passed into renderNextBlock(). Do not call it yourself. */ - virtual void notePitchbendChanged (MPENote changedNote) override; + void notePitchbendChanged (MPENote changedNote) override; /** Will find any voice that is currently playing changedNote, update its currently playing note, and call its noteTimbreChanged method. @@ -227,7 +227,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase This method will be called automatically according to the midi data passed into renderNextBlock(). Do not call it yourself. */ - virtual void noteTimbreChanged (MPENote changedNote) override; + void noteTimbreChanged (MPENote changedNote) override; /** Will find any voice that is currently playing changedNote, update its currently playing note, and call its noteKeyStateChanged method. @@ -235,24 +235,24 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase This method will be called automatically according to the midi data passed into renderNextBlock(). Do not call it yourself. */ - virtual void noteKeyStateChanged (MPENote changedNote) override; + void noteKeyStateChanged (MPENote changedNote) override; //============================================================================== /** This will simply call renderNextBlock for each currently active voice and fill the buffer with the sum. Override this method if you need to do more work to render your audio. */ - virtual void renderNextSubBlock (AudioBuffer& outputAudio, - int startSample, - int numSamples) override; + void renderNextSubBlock (AudioBuffer& outputAudio, + int startSample, + int numSamples) override; /** This will simply call renderNextBlock for each currently active - voice and fill the buffer with the sum. (souble-precision version) + voice and fill the buffer with the sum. (double-precision version) Override this method if you need to do more work to render your audio. */ - virtual void renderNextSubBlock (AudioBuffer& outputAudio, - int startSample, - int numSamples) override; + void renderNextSubBlock (AudioBuffer& outputAudio, + int startSample, + int numSamples) override; //============================================================================== /** Searches through the voices to find one that's not currently playing, and @@ -304,6 +304,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase private: //============================================================================== bool shouldStealVoices = false; + uint32 lastNoteOnCounter = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) }; diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp index 5cb03eee50..b20e13d661 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp @@ -42,11 +42,6 @@ bool MPESynthesiserVoice::isPlayingButReleased() const noexcept return isActive() && currentlyPlayingNote.keyState == MPENote::off; } -bool MPESynthesiserVoice::wasStartedBefore (const MPESynthesiserVoice& other) const noexcept -{ - return noteStartTime < other.noteStartTime; -} - void MPESynthesiserVoice::clearCurrentNote() noexcept { currentlyPlayingNote = MPENote(); diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h index f1603d5f44..febd84f036 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h @@ -156,8 +156,10 @@ class JUCE_API MPESynthesiserVoice */ double getSampleRate() const noexcept { return currentSampleRate; } - /** Returns true if this voice started playing its current note before the other voice did. */ - bool wasStartedBefore (const MPESynthesiserVoice& other) const noexcept; + /** This will be set to an incrementing counter value in MPESynthesiser::startVoice() + and can be used to determine the order in which voices started. + */ + uint32 noteOnTime = 0; protected: //============================================================================== @@ -182,7 +184,6 @@ class JUCE_API MPESynthesiserVoice private: //============================================================================== friend class MPESynthesiser; - uint32 noteStartTime = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) }; diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp index 18efd66f2d..8ebc185b70 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp @@ -49,7 +49,7 @@ MPEChannelAssigner::MPEChannelAssigner (Range channelRange) int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept { - if (numChannels == 1) + if (numChannels <= 1) return firstChannel; for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) @@ -84,15 +84,29 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept return midiChannelLastAssigned; } -void MPEChannelAssigner::noteOff (int noteNumber) +void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) { - for (auto& ch : midiChannels) + const auto removeNote = [] (MidiChannel& ch, int noteNum) { - if (ch.notes.removeAllInstancesOf (noteNumber) > 0) + if (ch.notes.removeAllInstancesOf (noteNum) > 0) { - ch.lastNotePlayed = noteNumber; - return; + ch.lastNotePlayed = noteNum; + return true; } + + return false; + }; + + if (midiChannel >= 0 && midiChannel < 17) + { + removeNote (midiChannels[midiChannel], noteNumber); + return; + } + + for (auto& ch : midiChannels) + { + if (removeNote (ch, noteNumber)) + return; } } @@ -255,6 +269,7 @@ void MPEChannelRemapper::zeroArrays() } } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -262,7 +277,7 @@ void MPEChannelRemapper::zeroArrays() struct MPEUtilsUnitTests : public UnitTest { MPEUtilsUnitTests() - : UnitTest ("MPE Utilities", "MIDI/MPE") + : UnitTest ("MPE Utilities", UnitTestCategories::midi) {} void runTest() override @@ -475,4 +490,5 @@ struct MPEUtilsUnitTests : public UnitTest static MPEUtilsUnitTests MPEUtilsUnitTests; #endif + } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.h index 7cbfd2b121..3f5f7dd7b1 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEUtils.h @@ -65,8 +65,11 @@ class MPEChannelAssigner /** You must call this method for all note-offs that you receive so that this class can keep track of the currently playing notes internally. + + You can specify the channel number the note off happened on. If you don't, it will + look through all channels to find the registered midi note matching the given note number. */ - void noteOff (int noteNumber); + void noteOff (int noteNumber, int midiChannel = -1); /** Call this to clear all currently playing notes. */ void allNotesOff(); diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEValue.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPEValue.cpp index b9a071303e..ccc20ab14c 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEValue.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEValue.cpp @@ -82,6 +82,7 @@ bool MPEValue::operator!= (const MPEValue& other) const noexcept return ! operator== (other); } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -89,7 +90,9 @@ bool MPEValue::operator!= (const MPEValue& other) const noexcept class MPEValueTests : public UnitTest { public: - MPEValueTests() : UnitTest ("MPEValue class", "MIDI/MPE") {} + MPEValueTests() + : UnitTest ("MPEValue class", UnitTestCategories::midi) + {} void runTest() override { @@ -165,6 +168,6 @@ class MPEValueTests : public UnitTest static MPEValueTests MPEValueUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index 7f94dee8b0..dfcba78e74 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -205,6 +205,7 @@ void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue, } } + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -212,7 +213,9 @@ void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue, class MPEZoneLayoutTests : public UnitTest { public: - MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", "MIDI/MPE") {} + MPEZoneLayoutTests() + : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi) + {} void runTest() override { @@ -382,6 +385,6 @@ class MPEZoneLayoutTests : public UnitTest static MPEZoneLayoutTests MPEZoneLayoutUnitTests; -#endif // JUCE_UNIT_TESTS +#endif } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h b/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h index 1414324ce3..58523ee5bb 100644 --- a/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h +++ b/JUCE/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h @@ -80,13 +80,7 @@ class JUCE_API MPEZoneLayout */ struct Zone { - Zone (const Zone& other) noexcept - : numMemberChannels (other.numMemberChannels), - perNotePitchbendRange (other.perNotePitchbendRange), - masterPitchbendRange (other.masterPitchbendRange), - lowerZone (other.lowerZone) - { - } + Zone (const Zone& other) = default; bool isLowerZone() const noexcept { return lowerZone; } bool isUpperZone() const noexcept { return ! lowerZone; } @@ -104,6 +98,11 @@ class JUCE_API MPEZoneLayout : (channel < 16 && channel >= 16 - numMemberChannels); } + bool isUsing (int channel) const noexcept + { + return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); + } + bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone && numMemberChannels == other.numMemberChannels && perNotePitchbendRange == other.perNotePitchbendRange @@ -185,7 +184,7 @@ class JUCE_API MPEZoneLayout { public: /** Destructor. */ - virtual ~Listener() {} + virtual ~Listener() = default; /** Implement this callback to be notified about any changes to this MPEZoneLayout. Will be called whenever a zone is added, zones are diff --git a/JUCE/modules/juce_audio_basics/sources/juce_AudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_AudioSource.h index 9633f67b83..401c4f772e 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_AudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_AudioSource.h @@ -32,9 +32,7 @@ namespace juce struct JUCE_API AudioSourceChannelInfo { /** Creates an uninitialised AudioSourceChannelInfo. */ - AudioSourceChannelInfo() noexcept - { - } + AudioSourceChannelInfo() = default; /** Creates an AudioSourceChannelInfo. */ AudioSourceChannelInfo (AudioBuffer* bufferToUse, @@ -113,18 +111,18 @@ class JUCE_API AudioSource protected: //============================================================================== /** Creates an AudioSource. */ - AudioSource() noexcept {} + AudioSource() = default; public: /** Destructor. */ - virtual ~AudioSource() {} + virtual ~AudioSource() = default; //============================================================================== /** Tells the source to prepare for playing. An AudioSource has two states: prepared and unprepared. - The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared' + The prepareToPlay() method is guaranteed to be called at least once on an 'unprepared' source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). This callback allows the source to initialise any resources it might need when playing. diff --git a/JUCE/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h index bc6f5abe9d..44c755d313 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h @@ -66,7 +66,7 @@ class JUCE_API BufferingAudioSource : public PositionableAudioSource, The input source may be deleted depending on whether the deleteSourceWhenDeleted flag was set in the constructor. */ - ~BufferingAudioSource(); + ~BufferingAudioSource() override; //============================================================================== /** Implementation of the AudioSource method. */ diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp b/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp index 3cec22131c..d5263ac55b 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp +++ b/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp @@ -145,9 +145,9 @@ void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInf } //============================================================================== -XmlElement* ChannelRemappingAudioSource::createXml() const +std::unique_ptr ChannelRemappingAudioSource::createXml() const { - XmlElement* e = new XmlElement ("MAPPINGS"); + auto e = std::make_unique ("MAPPINGS"); String ins, outs; const ScopedLock sl (lock); diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h index f46093b2f1..928a20c558 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h @@ -56,7 +56,7 @@ class ChannelRemappingAudioSource : public AudioSource bool deleteSourceWhenDeleted); /** Destructor. */ - ~ChannelRemappingAudioSource(); + ~ChannelRemappingAudioSource() override; //============================================================================== /** Specifies a number of channels that this audio source must produce from its @@ -112,7 +112,7 @@ class ChannelRemappingAudioSource : public AudioSource /** Returns an XML object to encapsulate the state of the mappings. @see restoreFromXml */ - XmlElement* createXml() const; + std::unique_ptr createXml() const; /** Restores the mappings from an XML object created by createXML(). @see createXml diff --git a/JUCE/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h index 35030c069c..e47e9e814d 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h @@ -43,7 +43,7 @@ class JUCE_API IIRFilterAudioSource : public AudioSource bool deleteInputWhenDeleted); /** Destructor. */ - ~IIRFilterAudioSource(); + ~IIRFilterAudioSource() override; //============================================================================== /** Changes the filter to use the same parameters as the one being passed in. */ diff --git a/JUCE/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp b/JUCE/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp index 6adec04448..a987d7f575 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp +++ b/JUCE/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp @@ -49,7 +49,8 @@ void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferT auto max = 0, pos = 0; auto n = buffer.getNumSamples(), m = bufferToFill.numSamples; - for (auto i = position; (i < n || isLooping) && (pos < m); i += max) + int i; + for (i = position; (i < n || isLooping) && (pos < m); i += max) { max = jmin (m - pos, n - (i % n)); @@ -65,6 +66,8 @@ void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferT if (pos < m) dst.clear (bufferToFill.startSample + pos, m - pos); + + position = (i % n); } } // namespace juce diff --git a/JUCE/modules/juce_audio_basics/sources/juce_MixerAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_MixerAudioSource.h index 8b3e9c27e5..d3766f938f 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_MixerAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_MixerAudioSource.h @@ -41,7 +41,7 @@ class JUCE_API MixerAudioSource : public AudioSource MixerAudioSource(); /** Destructor. */ - ~MixerAudioSource(); + ~MixerAudioSource() override; //============================================================================== /** Adds an input source to the mixer. diff --git a/JUCE/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h index 610d2c5af4..df7ea36328 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h @@ -40,11 +40,11 @@ class JUCE_API PositionableAudioSource : public AudioSource protected: //============================================================================== /** Creates the PositionableAudioSource. */ - PositionableAudioSource() noexcept {} + PositionableAudioSource() = default; public: /** Destructor */ - ~PositionableAudioSource() {} + ~PositionableAudioSource() override = default; //============================================================================== /** Tells the stream to move to a new position. diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp b/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp index 1377071359..d123e4cf2e 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp +++ b/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp @@ -27,11 +27,6 @@ ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted, const int channels) : input (inputSource, deleteInputWhenDeleted), - ratio (1.0), - lastRatio (1.0), - bufferPos (0), - sampsInBuffer (0), - subSampleOffset (0), numChannels (channels) { jassert (input != nullptr); @@ -67,6 +62,8 @@ void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double s void ResamplingAudioSource::flushBuffers() { + const ScopedLock sl (callbackLock); + buffer.clear(); bufferPos = 0; sampsInBuffer = 0; @@ -82,10 +79,12 @@ void ResamplingAudioSource::releaseResources() void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { + const ScopedLock sl (callbackLock); + double localRatio; { - const SpinLock::ScopedLockType sl (ratioLock); + const SpinLock::ScopedLockType ratioSl (ratioLock); localRatio = ratio; } diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h index 0ff3fae6b3..49b4974b29 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h @@ -47,7 +47,7 @@ class JUCE_API ResamplingAudioSource : public AudioSource int numChannels = 2); /** Destructor. */ - ~ResamplingAudioSource(); + ~ResamplingAudioSource() override; /** Changes the resampling ratio. @@ -76,12 +76,13 @@ class JUCE_API ResamplingAudioSource : public AudioSource private: //============================================================================== OptionalScopedPointer input; - double ratio, lastRatio; + double ratio = 1.0, lastRatio = 1.0; AudioBuffer buffer; - int bufferPos, sampsInBuffer; - double subSampleOffset; + int bufferPos = 0, sampsInBuffer = 0; + double subSampleOffset = 0.0; double coefficients[6]; SpinLock ratioLock; + CriticalSection callbackLock; const int numChannels; HeapBlock destBuffers; HeapBlock srcBuffers; diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h index 99fc422dbd..a22f4199ad 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h @@ -44,7 +44,7 @@ class JUCE_API ReverbAudioSource : public AudioSource bool deleteInputWhenDeleted); /** Destructor. */ - ~ReverbAudioSource(); + ~ReverbAudioSource() override; //============================================================================== /** Returns the parameters from the reverb. */ diff --git a/JUCE/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h b/JUCE/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h index ea1da6afa8..df04c89913 100644 --- a/JUCE/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h +++ b/JUCE/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h @@ -38,7 +38,7 @@ class JUCE_API ToneGeneratorAudioSource : public AudioSource ToneGeneratorAudioSource(); /** Destructor. */ - ~ToneGeneratorAudioSource(); + ~ToneGeneratorAudioSource() override; //============================================================================== /** Sets the signal's amplitude. */ diff --git a/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index fa047d1496..8c02bf246d 100644 --- a/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -217,6 +217,18 @@ void Synthesiser::processNextBlock (AudioBuffer& outputAudio, template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); +void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, + int startSample, int numSamples) +{ + processNextBlock (outputAudio, inputMidi, startSample, numSamples); +} + +void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, + int startSample, int numSamples) +{ + processNextBlock (outputAudio, inputMidi, startSample, numSamples); +} + void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) { for (auto* voice : voices) @@ -323,7 +335,7 @@ void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool voice->stopNote (velocity, allowTailOff); // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! - jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); + jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == nullptr)); } void Synthesiser::noteOff (const int midiChannel, @@ -338,7 +350,7 @@ void Synthesiser::noteOff (const int midiChannel, if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) { - if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound()) + if (auto sound = voice->getCurrentlyPlayingSound()) { if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) diff --git a/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index 3215d10002..7c39c8144e 100644 --- a/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/JUCE/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -46,7 +46,7 @@ class JUCE_API SynthesiserSound : public ReferenceCountedObject public: /** Destructor. */ - virtual ~SynthesiserSound(); + ~SynthesiserSound() override; //============================================================================== /** Returns true if this sound should be played when a given midi note is pressed. @@ -274,11 +274,6 @@ class JUCE_API SynthesiserVoice AudioBuffer tempBuffer; - #if JUCE_CATCH_DEPRECATED_CODE_MISUSE - // Note the new parameters for this method. - virtual int stopNote (bool) { return 0; } - #endif - JUCE_LEAK_DETECTOR (SynthesiserVoice) }; @@ -352,7 +347,7 @@ class JUCE_API Synthesiser int getNumSounds() const noexcept { return sounds.size(); } /** Returns one of the sounds. */ - SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; } + SynthesiserSound::Ptr getSound (int index) const noexcept { return sounds[index]; } /** Adds a new sound to the synthesiser. @@ -525,21 +520,15 @@ class JUCE_API Synthesiser both to the audio output buffer and the midi input buffer, so any midi events with timestamps outside the specified region will be ignored. */ - inline void renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples) - { - processNextBlock (outputAudio, inputMidi, startSample, numSamples); - } + void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); - inline void renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples) - { - processNextBlock (outputAudio, inputMidi, startSample, numSamples); - } + void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); /** Returns the current target sample rate at which rendering is being done. Subclasses may need to know this so that they can pitch things correctly. @@ -632,12 +621,6 @@ class JUCE_API Synthesiser private: //============================================================================== - template - void processNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples); - //============================================================================== double sampleRate = 0; uint32 lastNoteOnCounter = 0; int minimumSubBlockSize = 32; @@ -645,6 +628,9 @@ class JUCE_API Synthesiser bool shouldStealNotes = true; BigInteger sustainPedalsDown; + template + void processNextBlock (AudioBuffer&, const MidiBuffer&, int startSample, int numSamples); + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // Note the new parameters for these methods. virtual int findFreeVoice (const bool) const { return 0; } diff --git a/JUCE/modules/juce_audio_basics/utilities/juce_ADSR.h b/JUCE/modules/juce_audio_basics/utilities/juce_ADSR.h new file mode 100644 index 0000000000..8f50ceefda --- /dev/null +++ b/JUCE/modules/juce_audio_basics/utilities/juce_ADSR.h @@ -0,0 +1,248 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A very simple ADSR envelope class. + + To use it, call setSampleRate() with the current sample rate and give it some parameters + with setParameters() then call getNextSample() to get the envelope value to be applied + to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer. + + @tags{Audio} +*/ +class ADSR +{ +public: + //============================================================================== + ADSR() + { + setSampleRate (44100.0); + setParameters ({}); + } + + //============================================================================== + /** + Holds the parameters being used by an ADSR object. + + @tags{Audio} + */ + struct Parameters + { + /** Attack time in seconds. */ + float attack = 0.1f; + + /** Decay time in seconds. */ + float decay = 0.1f; + + /** Sustain level. */ + float sustain = 1.0f; + + /** Release time in seconds. */ + float release = 0.1f; + }; + + /** Sets the parameters that will be used by an ADSR object. + + You must have called setSampleRate() with the correct sample rate before + this otherwise the values may be incorrect! + + @see getParameters + */ + void setParameters (const Parameters& newParameters) + { + currentParameters = newParameters; + + sustainLevel = newParameters.sustain; + calculateRates (newParameters); + + if (currentState != State::idle) + checkCurrentState(); + } + + /** Returns the parameters currently being used by an ADSR object. + + @see setParameters + */ + const Parameters& getParameters() const { return currentParameters; } + + /** Returns true if the envelope is in its attack, decay, sustain or release stage. */ + bool isActive() const noexcept { return currentState != State::idle; } + + //============================================================================== + /** Sets the sample rate that will be used for the envelope. + + This must be called before the getNextSample() or setParameters() methods. + */ + void setSampleRate (double sampleRate) + { + jassert (sampleRate > 0.0); + sr = sampleRate; + } + + //============================================================================== + /** Resets the envelope to an idle state. */ + void reset() + { + envelopeVal = 0.0f; + currentState = State::idle; + } + + /** Starts the attack phase of the envelope. */ + void noteOn() + { + if (attackRate > 0.0f) + { + currentState = State::attack; + } + else if (decayRate > 0.0f) + { + envelopeVal = 1.0f; + currentState = State::decay; + } + else + { + currentState = State::sustain; + } + } + + /** Starts the release phase of the envelope. */ + void noteOff() + { + if (currentState != State::idle) + { + if (currentParameters.release > 0.0f) + { + releaseRate = static_cast (envelopeVal / (currentParameters.release * sr)); + currentState = State::release; + } + else + { + reset(); + } + } + } + + //============================================================================== + /** Returns the next sample value for an ADSR object. + + @see applyEnvelopeToBuffer + */ + float getNextSample() + { + if (currentState == State::idle) + return 0.0f; + + if (currentState == State::attack) + { + envelopeVal += attackRate; + + if (envelopeVal >= 1.0f) + { + envelopeVal = 1.0f; + + if (decayRate > 0.0f) + currentState = State::decay; + else + currentState = State::sustain; + } + } + else if (currentState == State::decay) + { + envelopeVal -= decayRate; + + if (envelopeVal <= sustainLevel) + { + envelopeVal = sustainLevel; + currentState = State::sustain; + } + } + else if (currentState == State::sustain) + { + envelopeVal = sustainLevel; + } + else if (currentState == State::release) + { + envelopeVal -= releaseRate; + + if (envelopeVal <= 0.0f) + reset(); + } + + return envelopeVal; + } + + /** This method will conveniently apply the next numSamples number of envelope values + to an AudioBuffer. + + @see getNextSample + */ + template + void applyEnvelopeToBuffer (AudioBuffer& buffer, int startSample, int numSamples) + { + jassert (startSample + numSamples <= buffer.getNumSamples()); + + auto numChannels = buffer.getNumChannels(); + + while (--numSamples >= 0) + { + auto env = getNextSample(); + + for (int i = 0; i < numChannels; ++i) + buffer.getWritePointer (i)[startSample] *= env; + + ++startSample; + } + } + +private: + //============================================================================== + void calculateRates (const Parameters& parameters) + { + // need to call setSampleRate() first! + jassert (sr > 0.0); + + attackRate = (parameters.attack > 0.0f ? static_cast (1.0f / (parameters.attack * sr)) : -1.0f); + decayRate = (parameters.decay > 0.0f ? static_cast ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f); + } + + void checkCurrentState() + { + if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain; + else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain; + else if (currentState == State::release && releaseRate <= 0.0f) reset(); + } + + //============================================================================== + enum class State { idle, attack, decay, sustain, release }; + + State currentState = State::idle; + Parameters currentParameters; + + double sr = 0.0; + float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; +}; + +} // namespace juce diff --git a/JUCE/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp b/JUCE/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp similarity index 100% rename from JUCE/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp rename to JUCE/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp diff --git a/JUCE/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h b/JUCE/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h similarity index 95% rename from JUCE/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h rename to JUCE/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h index ab3ca754f2..2d89a34efa 100644 --- a/JUCE/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h +++ b/JUCE/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h @@ -42,6 +42,9 @@ class JUCE_API CatmullRomInterpolator CatmullRomInterpolator() noexcept; ~CatmullRomInterpolator() noexcept; + CatmullRomInterpolator (CatmullRomInterpolator&&) noexcept = default; + CatmullRomInterpolator& operator= (CatmullRomInterpolator&&) noexcept = default; + /** Resets the state of the interpolator. Call this when there's a break in the continuity of the input data stream. */ diff --git a/JUCE/modules/juce_audio_basics/effects/juce_Decibels.h b/JUCE/modules/juce_audio_basics/utilities/juce_Decibels.h similarity index 100% rename from JUCE/modules/juce_audio_basics/effects/juce_Decibels.h rename to JUCE/modules/juce_audio_basics/utilities/juce_Decibels.h diff --git a/JUCE/modules/juce_audio_basics/effects/juce_IIRFilter.cpp b/JUCE/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp similarity index 100% rename from JUCE/modules/juce_audio_basics/effects/juce_IIRFilter.cpp rename to JUCE/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp diff --git a/JUCE/modules/juce_audio_basics/effects/juce_IIRFilter.h b/JUCE/modules/juce_audio_basics/utilities/juce_IIRFilter.h similarity index 100% rename from JUCE/modules/juce_audio_basics/effects/juce_IIRFilter.h rename to JUCE/modules/juce_audio_basics/utilities/juce_IIRFilter.h diff --git a/JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp b/JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp similarity index 96% rename from JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp rename to JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp index 60227f545d..952327c72a 100644 --- a/JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp +++ b/JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp @@ -242,6 +242,10 @@ namespace } subSamplePos = pos; + + if (wrap == 0) + return (int) (in - originalIn); + return ((int) (in - originalIn) + wrap) % wrap; } @@ -353,6 +357,10 @@ namespace } subSamplePos = pos; + + if (wrap == 0) + return (int) (in - originalIn); + return ((int) (in - originalIn) + wrap) % wrap; } diff --git a/JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h b/JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h similarity index 95% rename from JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h rename to JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h index 6914e465c0..25598e7500 100644 --- a/JUCE/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h +++ b/JUCE/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h @@ -42,6 +42,9 @@ class JUCE_API LagrangeInterpolator LagrangeInterpolator() noexcept; ~LagrangeInterpolator() noexcept; + LagrangeInterpolator (LagrangeInterpolator&&) noexcept = default; + LagrangeInterpolator& operator= (LagrangeInterpolator&&) noexcept = default; + /** Resets the state of the interpolator. Call this when there's a break in the continuity of the input data stream. */ diff --git a/JUCE/modules/juce_audio_basics/effects/juce_Reverb.h b/JUCE/modules/juce_audio_basics/utilities/juce_Reverb.h similarity index 84% rename from JUCE/modules/juce_audio_basics/effects/juce_Reverb.h rename to JUCE/modules/juce_audio_basics/utilities/juce_Reverb.h index aabcad8757..0ed72b80f2 100644 --- a/JUCE/modules/juce_audio_basics/effects/juce_Reverb.h +++ b/JUCE/modules/juce_audio_basics/utilities/juce_Reverb.h @@ -49,22 +49,13 @@ class Reverb /** Holds the parameters being used by a Reverb object. */ struct Parameters { - Parameters() noexcept - : roomSize (0.5f), - damping (0.5f), - wetLevel (0.33f), - dryLevel (0.4f), - width (1.0f), - freezeMode (0) - {} - - float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ - float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ - float wetLevel; /**< Wet level, 0 to 1.0 */ - float dryLevel; /**< Dry level, 0 to 1.0 */ - float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ - float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 - put the reverb into a continuous feedback loop. */ + float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ + float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ + float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */ + float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */ + float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ + float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 + put the reverb into a continuous feedback loop. */ }; //============================================================================== @@ -81,9 +72,9 @@ class Reverb const float dryScaleFactor = 2.0f; const float wet = newParams.wetLevel * wetScaleFactor; - dryGain.setValue (newParams.dryLevel * dryScaleFactor); - wetGain1.setValue (0.5f * wet * (1.0f + newParams.width)); - wetGain2.setValue (0.5f * wet * (1.0f - newParams.width)); + dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor); + wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width)); + wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width)); gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; parameters = newParams; @@ -216,15 +207,15 @@ class Reverb void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept { - damping.setValue (dampingToUse); - feedback.setValue (roomSizeToUse); + damping.setTargetValue (dampingToUse); + feedback.setTargetValue (roomSizeToUse); } //============================================================================== class CombFilter { public: - CombFilter() noexcept : bufferSize (0), bufferIndex (0), last (0) {} + CombFilter() noexcept {} void setSize (const int size) { @@ -259,8 +250,8 @@ class Reverb private: HeapBlock buffer; - int bufferSize, bufferIndex; - float last; + int bufferSize = 0, bufferIndex = 0; + float last = 0.0f; JUCE_DECLARE_NON_COPYABLE (CombFilter) }; @@ -269,7 +260,7 @@ class Reverb class AllPassFilter { public: - AllPassFilter() noexcept : bufferSize (0), bufferIndex (0) {} + AllPassFilter() noexcept {} void setSize (const int size) { @@ -300,7 +291,7 @@ class Reverb private: HeapBlock buffer; - int bufferSize, bufferIndex; + int bufferSize = 0, bufferIndex = 0; JUCE_DECLARE_NON_COPYABLE (AllPassFilter) }; @@ -314,7 +305,7 @@ class Reverb CombFilter comb [numChannels][numCombs]; AllPassFilter allPass [numChannels][numAllPasses]; - LinearSmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; + SmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) }; diff --git a/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp b/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp new file mode 100644 index 0000000000..643dfd19b7 --- /dev/null +++ b/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2018 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_UNIT_TESTS + +static CommonSmoothedValueTests > commonLinearSmoothedValueTests; +static CommonSmoothedValueTests > commonMultiplicativeSmoothedValueTests; + +class SmoothedValueTests : public UnitTest +{ +public: + SmoothedValueTests() + : UnitTest ("SmoothedValueTests", UnitTestCategories::smoothedValues) + {} + + void runTest() override + { + beginTest ("Linear moving target"); + { + SmoothedValue sv; + + sv.reset (12); + float initialValue = 0.0f; + sv.setCurrentAndTargetValue (initialValue); + sv.setTargetValue (1.0f); + + auto delta = sv.getNextValue() - initialValue; + + sv.skip (6); + + auto newInitialValue = sv.getCurrentValue(); + sv.setTargetValue (newInitialValue + 2.0f); + auto doubleDelta = sv.getNextValue() - newInitialValue; + + expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); + } + + beginTest ("Multiplicative curve"); + { + SmoothedValue sv; + + auto numSamples = 12; + AudioBuffer values (2, numSamples + 1); + + sv.reset (numSamples); + sv.setCurrentAndTargetValue (1.0); + sv.setTargetValue (2.0f); + + values.setSample (0, 0, sv.getCurrentValue()); + + for (int i = 1; i < values.getNumSamples(); ++i) + values.setSample (0, i, sv.getNextValue()); + + sv.setTargetValue (1.0f); + values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue()); + + for (int i = values.getNumSamples() - 2; i >= 0 ; --i) + values.setSample (1, i, sv.getNextValue()); + + for (int i = 0; i < values.getNumSamples(); ++i) + expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9); + } + } +}; + +static SmoothedValueTests smoothedValueTests; + +#endif + +} // namespace juce diff --git a/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.h b/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.h new file mode 100644 index 0000000000..a472fb00c2 --- /dev/null +++ b/JUCE/modules/juce_audio_basics/utilities/juce_SmoothedValue.h @@ -0,0 +1,630 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A base class for the smoothed value classes. + + This class is used to provide common functionality to the SmoothedValue and + dsp::LogRampedValue classes. + + @tags{Audio} +*/ +template +class SmoothedValueBase +{ +private: + //============================================================================== + template struct FloatTypeHelper; + + template