fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
parent
a2be47c887
commit
dff4d13a1d
File diff suppressed because it is too large
Load Diff
|
@ -1,29 +1,29 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist>
|
<plist>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>uk.co.birchlabs.juicysfplugin</string>
|
<string>uk.co.birchlabs.juicysfplugin</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>juicysfplugin</string>
|
<string>juicysfplugin</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>juicysfplugin</string>
|
<string>juicysfplugin</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.0</string>
|
<string>1.0.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0.0</string>
|
<string>1.0.0</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -304,7 +304,7 @@
|
||||||
#define JucePlugin_Build_AU 0
|
#define JucePlugin_Build_AU 0
|
||||||
#endif
|
#endif
|
||||||
#ifndef JucePlugin_Build_AUv3
|
#ifndef JucePlugin_Build_AUv3
|
||||||
#define JucePlugin_Build_AUv3 1
|
#define JucePlugin_Build_AUv3 0
|
||||||
#endif
|
#endif
|
||||||
#ifndef JucePlugin_Build_RTAS
|
#ifndef JucePlugin_Build_RTAS
|
||||||
#define JucePlugin_Build_RTAS 0
|
#define JucePlugin_Build_RTAS 0
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<JUCERPROJECT name="juicysfplugin" projectType="audioplug" buildStandalone="1"
|
<JUCERPROJECT name="juicysfplugin" projectType="audioplug" buildStandalone="1"
|
||||||
jucerVersion="5.3.2" buildAUv3="1" buildVST3="1" pluginIsSynth="1"
|
jucerVersion="5.3.2" buildAUv3="0" buildVST3="1" pluginIsSynth="1"
|
||||||
pluginWantsMidiIn="1" pluginEditorRequiresKeys="1" companyName="Birchlabs"
|
pluginWantsMidiIn="1" pluginEditorRequiresKeys="1" companyName="Birchlabs"
|
||||||
companyWebsite="https://birchlabs.co.uk" bundleIdentifier="uk.co.birchlabs.juicysfplugin"
|
companyWebsite="https://birchlabs.co.uk" bundleIdentifier="uk.co.birchlabs.juicysfplugin"
|
||||||
pluginManufacturer="birchlabs" pluginCode="Jspf" pluginManufacturerCode="Blbs"
|
pluginManufacturer="birchlabs" pluginCode="Jspf" pluginManufacturerCode="Blbs"
|
||||||
pluginName="Juicy SF" pluginDesc="Audio plugin to play soundfonts"
|
pluginName="Juicy SF" pluginDesc="Audio plugin to play soundfonts"
|
||||||
id="ptvVfg" pluginFormats="buildVST,buildVST3,buildAUv3,buildStandalone"
|
id="ptvVfg" pluginFormats="buildStandalone,buildVST,buildVST3"
|
||||||
pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn,pluginEditorRequiresKeys"
|
pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn,pluginEditorRequiresKeys"
|
||||||
buildVST="1" buildAU="0" buildRTAS="0" buildAAX="0" enableIAA="0">
|
buildVST="1" buildAU="0" buildRTAS="0" buildAAX="0" enableIAA="0">
|
||||||
<MAINGROUP id="rCqBG3" name="juicysfplugin">
|
<MAINGROUP id="rCqBG3" name="juicysfplugin">
|
||||||
|
@ -65,8 +65,8 @@
|
||||||
<XCODE_MAC targetFolder="Builds/MacOSX" vst3Folder="" externalLibraries="fluidsynth"
|
<XCODE_MAC targetFolder="Builds/MacOSX" vst3Folder="" externalLibraries="fluidsynth"
|
||||||
extraLinkerFlags="-Llib_relinked">
|
extraLinkerFlags="-Llib_relinked">
|
||||||
<CONFIGURATIONS>
|
<CONFIGURATIONS>
|
||||||
<CONFIGURATION isDebug="1" name="Debug" headerPath="../../include"/>
|
<CONFIGURATION isDebug="1" name="Debug" headerPath="../../include ../../modules"/>
|
||||||
<CONFIGURATION isDebug="0" name="Release" headerPath="../../include"/>
|
<CONFIGURATION isDebug="0" name="Release" headerPath="../../include ../../modules"/>
|
||||||
</CONFIGURATIONS>
|
</CONFIGURATIONS>
|
||||||
<MODULEPATHS>
|
<MODULEPATHS>
|
||||||
<MODULEPATH id="juce_core" path="../../../../Applications/JUCE/modules"/>
|
<MODULEPATH id="juce_core" path="../../../../Applications/JUCE/modules"/>
|
||||||
|
|
203
modules/JUCE Module Format.txt
Normal file
203
modules/JUCE Module Format.txt
Normal file
|
@ -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:
|
||||||
|
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
|
||||||
|
|
78
modules/juce_analytics/analytics/juce_Analytics.cpp
Normal file
78
modules/juce_analytics/analytics/juce_Analytics.cpp
Normal file
|
@ -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<AnalyticsDestination>& 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)
|
||||||
|
|
||||||
|
}
|
117
modules/juce_analytics/analytics/juce_Analytics.h
Normal file
117
modules/juce_analytics/analytics/juce_Analytics.h
Normal file
|
@ -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<AnalyticsDestination>& 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() { clearSingletonInstance(); }
|
||||||
|
|
||||||
|
String userId;
|
||||||
|
StringPairArray userProperties;
|
||||||
|
|
||||||
|
bool isSuspended = false;
|
||||||
|
|
||||||
|
OwnedArray<AnalyticsDestination> destinations;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Analytics)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
61
modules/juce_analytics/analytics/juce_ButtonTracker.cpp
Normal file
61
modules/juce_analytics/analytics/juce_ButtonTracker.cpp
Normal file
|
@ -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
|
80
modules/juce_analytics/analytics/juce_ButtonTracker.h
Normal file
80
modules/juce_analytics/analytics/juce_ButtonTracker.h
Normal file
|
@ -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
|
||||||
|
singeton 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();
|
||||||
|
|
||||||
|
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
|
|
@ -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() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
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
|
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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& threadName,
|
||||||
|
ThreadedAnalyticsDestination& destination)
|
||||||
|
: Thread (threadName),
|
||||||
|
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<AnalyticsEvent> 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<AnalyticsEvent>& loggedEvents,
|
||||||
|
std::deque<AnalyticsEvent>& unloggedEvents)
|
||||||
|
: ThreadedAnalyticsDestination ("ThreadedAnalyticsDestinationTest"),
|
||||||
|
loggedEventQueue (loggedEvents),
|
||||||
|
unloggedEventStore (unloggedEvents)
|
||||||
|
{
|
||||||
|
startAnalyticsThread (20);
|
||||||
|
}
|
||||||
|
|
||||||
|
~BasicDestination()
|
||||||
|
{
|
||||||
|
stopAnalyticsThread (1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getMaximumBatchSize() override
|
||||||
|
{
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
|
||||||
|
{
|
||||||
|
unloggedEventStore = eventsToSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
|
||||||
|
{
|
||||||
|
restoredEventQueue = unloggedEventStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logBatchedEvents (const Array<AnalyticsEvent>& 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<AnalyticsEvent>& loggedEventQueue;
|
||||||
|
std::deque<AnalyticsEvent>& unloggedEventStore;
|
||||||
|
bool loggingIsEnabled = true;
|
||||||
|
CriticalSection eventQueueChanging;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
struct ThreadedAnalyticsDestinationTests : public UnitTest
|
||||||
|
{
|
||||||
|
ThreadedAnalyticsDestinationTests()
|
||||||
|
: UnitTest ("ThreadedAnalyticsDestination")
|
||||||
|
{}
|
||||||
|
|
||||||
|
void compareEventQueues (const std::deque<AnalyticsDestination::AnalyticsEvent>& a,
|
||||||
|
const std::deque<AnalyticsDestination::AnalyticsEvent>& 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<AnalyticsDestination::AnalyticsEvent> testEvents;
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; ++i)
|
||||||
|
testEvents.push_back ({ String (i), 0, Time::getMillisecondCounter(), {}, "TestUser", {} });
|
||||||
|
|
||||||
|
std::deque<AnalyticsDestination::AnalyticsEvent> 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
|
|
@ -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. */
|
||||||
|
virtual ~ThreadedAnalyticsDestination();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
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<AnalyticsEvent>& 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<AnalyticsEvent>& 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<AnalyticsEvent>& restoredEventQueue) = 0;
|
||||||
|
|
||||||
|
struct EventDispatcher : public Thread
|
||||||
|
{
|
||||||
|
EventDispatcher (const String& threadName, ThreadedAnalyticsDestination&);
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
void addToQueue (const AnalyticsEvent&);
|
||||||
|
|
||||||
|
ThreadedAnalyticsDestination& parent;
|
||||||
|
|
||||||
|
std::deque<AnalyticsEvent> eventQueue;
|
||||||
|
CriticalSection queueAccess;
|
||||||
|
|
||||||
|
Atomic<int> batchPeriodMilliseconds { 1000 };
|
||||||
|
|
||||||
|
Array<AnalyticsEvent> eventsToSend;
|
||||||
|
};
|
||||||
|
|
||||||
|
const String destinationName;
|
||||||
|
EventDispatcher dispatcher;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadedAnalyticsDestination)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
40
modules/juce_analytics/juce_analytics.cpp
Normal file
40
modules/juce_analytics/juce_analytics.cpp
Normal file
|
@ -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"
|
60
modules/juce_analytics/juce_analytics.h
Normal file
60
modules/juce_analytics/juce_analytics.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.3.2
|
||||||
|
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 <queue>
|
||||||
|
#include <juce_gui_basics/juce_gui_basics.h>
|
||||||
|
|
||||||
|
#include "destinations/juce_AnalyticsDestination.h"
|
||||||
|
#include "destinations/juce_ThreadedAnalyticsDestination.h"
|
||||||
|
#include "analytics/juce_Analytics.h"
|
||||||
|
#include "analytics/juce_ButtonTracker.h"
|
157
modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h
Normal file
157
modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 subclass of AudioPlayHead can supply information about the position and
|
||||||
|
status of a moving play head during audio playback.
|
||||||
|
|
||||||
|
One of these can be supplied to an AudioProcessor object so that it can find
|
||||||
|
out about the position of the audio that it is rendering.
|
||||||
|
|
||||||
|
@see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioPlayHead
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
AudioPlayHead() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~AudioPlayHead() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Frame rate types. */
|
||||||
|
enum FrameRateType
|
||||||
|
{
|
||||||
|
fps23976 = 0,
|
||||||
|
fps24 = 1,
|
||||||
|
fps25 = 2,
|
||||||
|
fps2997 = 3,
|
||||||
|
fps30 = 4,
|
||||||
|
fps2997drop = 5,
|
||||||
|
fps30drop = 6,
|
||||||
|
fps60 = 7,
|
||||||
|
fps60drop = 8,
|
||||||
|
fpsUnknown = 99
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method.
|
||||||
|
*/
|
||||||
|
struct JUCE_API CurrentPositionInfo
|
||||||
|
{
|
||||||
|
/** The tempo in BPM */
|
||||||
|
double bpm;
|
||||||
|
|
||||||
|
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */
|
||||||
|
int timeSigNumerator;
|
||||||
|
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */
|
||||||
|
int timeSigDenominator;
|
||||||
|
|
||||||
|
/** The current play position, in samples from the start of the timeline. */
|
||||||
|
int64 timeInSamples;
|
||||||
|
/** The current play position, in seconds from the start of the timeline. */
|
||||||
|
double timeInSeconds;
|
||||||
|
|
||||||
|
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */
|
||||||
|
double editOriginTime;
|
||||||
|
|
||||||
|
/** The current play position, in units of quarter-notes. */
|
||||||
|
double ppqPosition;
|
||||||
|
|
||||||
|
/** The position of the start of the last bar, in units of quarter-notes.
|
||||||
|
|
||||||
|
This is the time from the start of the timeline to the start of the current
|
||||||
|
bar, in ppq units.
|
||||||
|
|
||||||
|
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If
|
||||||
|
it's not available, the value will be 0.
|
||||||
|
*/
|
||||||
|
double ppqPositionOfLastBarStart;
|
||||||
|
|
||||||
|
/** The video frame rate, if applicable. */
|
||||||
|
FrameRateType frameRate;
|
||||||
|
|
||||||
|
/** True if the transport is currently playing. */
|
||||||
|
bool isPlaying;
|
||||||
|
|
||||||
|
/** True if the transport is currently recording.
|
||||||
|
|
||||||
|
(When isRecording is true, then isPlaying will also be true).
|
||||||
|
*/
|
||||||
|
bool isRecording;
|
||||||
|
|
||||||
|
/** The current cycle start position in units of quarter-notes.
|
||||||
|
Note that not all hosts or plugin formats may provide this value.
|
||||||
|
@see isLooping
|
||||||
|
*/
|
||||||
|
double ppqLoopStart;
|
||||||
|
|
||||||
|
/** The current cycle end position in units of quarter-notes.
|
||||||
|
Note that not all hosts or plugin formats may provide this value.
|
||||||
|
@see isLooping
|
||||||
|
*/
|
||||||
|
double ppqLoopEnd;
|
||||||
|
|
||||||
|
/** True if the transport is currently looping. */
|
||||||
|
bool isLooping;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool operator== (const CurrentPositionInfo& other) const noexcept;
|
||||||
|
bool operator!= (const CurrentPositionInfo& other) const noexcept;
|
||||||
|
|
||||||
|
void resetToDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Fills-in the given structure with details about the transport's
|
||||||
|
position at the start of the current processing block. If this method returns
|
||||||
|
false then the current play head position is not available and the given
|
||||||
|
structure will be undefined.
|
||||||
|
|
||||||
|
You can ONLY call this from your processBlock() method! Calling it at other
|
||||||
|
times will produce undefined behaviour, as the host may not have any context
|
||||||
|
in which a time would make sense, and some hosts will almost certainly have
|
||||||
|
multithreading issues if it's not called on the audio thread.
|
||||||
|
*/
|
||||||
|
virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0;
|
||||||
|
|
||||||
|
/** Returns true if this object can control the transport. */
|
||||||
|
virtual bool canControlTransport() { return false; }
|
||||||
|
|
||||||
|
/** Starts or stops the audio. */
|
||||||
|
virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); }
|
||||||
|
|
||||||
|
/** Starts or stops recording the audio. */
|
||||||
|
virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); }
|
||||||
|
|
||||||
|
/** Rewinds the audio. */
|
||||||
|
virtual void transportRewind() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
586
modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp
Normal file
586
modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp
Normal file
|
@ -0,0 +1,586 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast<int64> (c))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet::AudioChannelSet (const Array<ChannelType>& c)
|
||||||
|
{
|
||||||
|
for (auto channel : c)
|
||||||
|
addChannel (channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; }
|
||||||
|
bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; }
|
||||||
|
bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; }
|
||||||
|
|
||||||
|
String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type)
|
||||||
|
{
|
||||||
|
if (type >= discreteChannel0)
|
||||||
|
return "Discrete " + String (type - discreteChannel0 + 1);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case left: return NEEDS_TRANS("Left");
|
||||||
|
case right: return NEEDS_TRANS("Right");
|
||||||
|
case centre: return NEEDS_TRANS("Centre");
|
||||||
|
case LFE: return NEEDS_TRANS("LFE");
|
||||||
|
case leftSurround: return NEEDS_TRANS("Left Surround");
|
||||||
|
case rightSurround: return NEEDS_TRANS("Right Surround");
|
||||||
|
case leftCentre: return NEEDS_TRANS("Left Centre");
|
||||||
|
case rightCentre: return NEEDS_TRANS("Right Centre");
|
||||||
|
case centreSurround: return NEEDS_TRANS("Centre Surround");
|
||||||
|
case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear");
|
||||||
|
case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear");
|
||||||
|
case topMiddle: return NEEDS_TRANS("Top Middle");
|
||||||
|
case topFrontLeft: return NEEDS_TRANS("Top Front Left");
|
||||||
|
case topFrontCentre: return NEEDS_TRANS("Top Front Centre");
|
||||||
|
case topFrontRight: return NEEDS_TRANS("Top Front Right");
|
||||||
|
case topRearLeft: return NEEDS_TRANS("Top Rear Left");
|
||||||
|
case topRearCentre: return NEEDS_TRANS("Top Rear Centre");
|
||||||
|
case topRearRight: return NEEDS_TRANS("Top Rear Right");
|
||||||
|
case wideLeft: return NEEDS_TRANS("Wide Left");
|
||||||
|
case wideRight: return NEEDS_TRANS("Wide Right");
|
||||||
|
case LFE2: return NEEDS_TRANS("LFE 2");
|
||||||
|
case leftSurroundSide: return NEEDS_TRANS("Left Surround Side");
|
||||||
|
case rightSurroundSide: return NEEDS_TRANS("Right Surround Side");
|
||||||
|
case ambisonicW: return NEEDS_TRANS("Ambisonic W");
|
||||||
|
case ambisonicX: return NEEDS_TRANS("Ambisonic X");
|
||||||
|
case ambisonicY: return NEEDS_TRANS("Ambisonic Y");
|
||||||
|
case ambisonicZ: return NEEDS_TRANS("Ambisonic Z");
|
||||||
|
case topSideLeft: return NEEDS_TRANS("Top Side Left");
|
||||||
|
case topSideRight: return NEEDS_TRANS("Top Side Right");
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type)
|
||||||
|
{
|
||||||
|
if (type >= discreteChannel0)
|
||||||
|
return String (type - discreteChannel0 + 1);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case left: return "L";
|
||||||
|
case right: return "R";
|
||||||
|
case centre: return "C";
|
||||||
|
case LFE: return "Lfe";
|
||||||
|
case leftSurround: return "Ls";
|
||||||
|
case rightSurround: return "Rs";
|
||||||
|
case leftCentre: return "Lc";
|
||||||
|
case rightCentre: return "Rc";
|
||||||
|
case centreSurround: return "Cs";
|
||||||
|
case leftSurroundRear: return "Lrs";
|
||||||
|
case rightSurroundRear: return "Rrs";
|
||||||
|
case topMiddle: return "Tm";
|
||||||
|
case topFrontLeft: return "Tfl";
|
||||||
|
case topFrontCentre: return "Tfc";
|
||||||
|
case topFrontRight: return "Tfr";
|
||||||
|
case topRearLeft: return "Trl";
|
||||||
|
case topRearCentre: return "Trc";
|
||||||
|
case topRearRight: return "Trr";
|
||||||
|
case wideLeft: return "Wl";
|
||||||
|
case wideRight: return "Wr";
|
||||||
|
case LFE2: return "Lfe2";
|
||||||
|
case leftSurroundSide: return "Lss";
|
||||||
|
case rightSurroundSide: return "Rss";
|
||||||
|
case ambisonicACN0: return "ACN0";
|
||||||
|
case ambisonicACN1: return "ACN1";
|
||||||
|
case ambisonicACN2: return "ACN2";
|
||||||
|
case ambisonicACN3: return "ACN3";
|
||||||
|
case topSideLeft: return "Tsl";
|
||||||
|
case topSideRight: return "Tsr";
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type >= ambisonicACN4 && type <= ambisonicACN35)
|
||||||
|
return "ACN" + String (type - ambisonicACN4 + 4);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr)
|
||||||
|
{
|
||||||
|
if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9'))
|
||||||
|
return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (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;
|
||||||
|
|
||||||
|
return unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioChannelSet::getSpeakerArrangementAsString() const
|
||||||
|
{
|
||||||
|
StringArray speakerTypes;
|
||||||
|
|
||||||
|
for (auto& speaker : getChannelTypes())
|
||||||
|
{
|
||||||
|
auto name = getAbbreviatedChannelTypeName (speaker);
|
||||||
|
|
||||||
|
if (name.isNotEmpty())
|
||||||
|
speakerTypes.add (name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return speakerTypes.joinIntoString (" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str)
|
||||||
|
{
|
||||||
|
AudioChannelSet set;
|
||||||
|
|
||||||
|
for (auto& abbr : StringArray::fromTokens (str, true))
|
||||||
|
{
|
||||||
|
auto type = getChannelTypeFromAbbreviation (abbr);
|
||||||
|
|
||||||
|
if (type != unknown)
|
||||||
|
set.addChannel (type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioChannelSet::getDescription() const
|
||||||
|
{
|
||||||
|
if (isDiscreteLayout()) return "Discrete #" + String (size());
|
||||||
|
if (*this == disabled()) return "Disabled";
|
||||||
|
if (*this == mono()) return "Mono";
|
||||||
|
if (*this == stereo()) return "Stereo";
|
||||||
|
|
||||||
|
if (*this == createLCR()) return "LCR";
|
||||||
|
if (*this == createLRS()) return "LRS";
|
||||||
|
if (*this == createLCRS()) return "LCRS";
|
||||||
|
|
||||||
|
if (*this == create5point0()) return "5.0 Surround";
|
||||||
|
if (*this == create5point1()) return "5.1 Surround";
|
||||||
|
if (*this == create6point0()) return "6.0 Surround";
|
||||||
|
if (*this == create6point1()) return "6.1 Surround";
|
||||||
|
if (*this == create6point0Music()) return "6.0 (Music) Surround";
|
||||||
|
if (*this == create6point1Music()) return "6.1 (Music) Surround";
|
||||||
|
if (*this == create7point0()) return "7.0 Surround";
|
||||||
|
if (*this == create7point1()) return "7.1 Surround";
|
||||||
|
if (*this == create7point0SDDS()) return "7.0 Surround SDDS";
|
||||||
|
if (*this == create7point1SDDS()) return "7.1 Surround SDDS";
|
||||||
|
if (*this == create7point0point2()) return "7.0.2 Surround";
|
||||||
|
if (*this == create7point1point2()) return "7.1.2 Surround";
|
||||||
|
|
||||||
|
if (*this == quadraphonic()) return "Quadraphonic";
|
||||||
|
if (*this == pentagonal()) return "Pentagonal";
|
||||||
|
if (*this == hexagonal()) return "Hexagonal";
|
||||||
|
if (*this == octagonal()) return "Octagonal";
|
||||||
|
|
||||||
|
// ambisonics
|
||||||
|
{
|
||||||
|
auto order = getAmbisonicOrder();
|
||||||
|
|
||||||
|
if (order >= 0)
|
||||||
|
{
|
||||||
|
String suffix;
|
||||||
|
|
||||||
|
switch (order)
|
||||||
|
{
|
||||||
|
case 1: suffix = "st"; break;
|
||||||
|
case 2: suffix = "nd"; break;
|
||||||
|
case 3: suffix = "rd"; break;
|
||||||
|
default: suffix = "th"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String (order) + suffix + " Order Ambisonics";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioChannelSet::isDiscreteLayout() const noexcept
|
||||||
|
{
|
||||||
|
for (auto& speaker : getChannelTypes())
|
||||||
|
if (speaker <= ambisonicACN35)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioChannelSet::size() const noexcept
|
||||||
|
{
|
||||||
|
return channels.countNumberOfSetBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept
|
||||||
|
{
|
||||||
|
int bit = channels.findNextSetBit(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < index && bit >= 0; ++i)
|
||||||
|
bit = channels.findNextSetBit (bit + 1);
|
||||||
|
|
||||||
|
return static_cast<ChannelType> (bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1))
|
||||||
|
{
|
||||||
|
if (static_cast<ChannelType> (bit) == type)
|
||||||
|
return idx;
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const
|
||||||
|
{
|
||||||
|
Array<ChannelType> result;
|
||||||
|
|
||||||
|
for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1))
|
||||||
|
result.add (static_cast<ChannelType> (bit));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioChannelSet::addChannel (ChannelType newChannel)
|
||||||
|
{
|
||||||
|
const int bit = static_cast<int> (newChannel);
|
||||||
|
jassert (bit >= 0 && bit < 1024);
|
||||||
|
channels.setBit (bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioChannelSet::removeChannel (ChannelType newChannel)
|
||||||
|
{
|
||||||
|
const int bit = static_cast<int> (newChannel);
|
||||||
|
jassert (bit >= 0 && bit < 1024);
|
||||||
|
channels.clearBit (bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::disabled() { return {}; }
|
||||||
|
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); }
|
||||||
|
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); }
|
||||||
|
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); }
|
||||||
|
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); }
|
||||||
|
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); }
|
||||||
|
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
|
||||||
|
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << centreSurround) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
|
||||||
|
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); }
|
||||||
|
AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); }
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::ambisonic (int order)
|
||||||
|
{
|
||||||
|
jassert (isPositiveAndBelow (order, 6));
|
||||||
|
|
||||||
|
if (order == 0)
|
||||||
|
return AudioChannelSet ((uint32) (1 << ambisonicACN0));
|
||||||
|
|
||||||
|
AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3));
|
||||||
|
|
||||||
|
auto numAmbisonicChannels = (order + 1) * (order + 1);
|
||||||
|
set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true);
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioChannelSet::getAmbisonicOrder() const
|
||||||
|
{
|
||||||
|
auto ambisonicOrder = getAmbisonicOrderForNumChannels (size());
|
||||||
|
|
||||||
|
if (ambisonicOrder >= 0)
|
||||||
|
return (*this == ambisonic (ambisonicOrder) ? ambisonicOrder : -1);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::discreteChannels (int numChannels)
|
||||||
|
{
|
||||||
|
AudioChannelSet s;
|
||||||
|
s.channels.setRange (discreteChannel0, numChannels, true);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels)
|
||||||
|
{
|
||||||
|
if (numChannels == 1) return AudioChannelSet::mono();
|
||||||
|
if (numChannels == 2) return AudioChannelSet::stereo();
|
||||||
|
if (numChannels == 3) return AudioChannelSet::createLCR();
|
||||||
|
if (numChannels == 4) return AudioChannelSet::quadraphonic();
|
||||||
|
if (numChannels == 5) return AudioChannelSet::create5point0();
|
||||||
|
if (numChannels == 6) return AudioChannelSet::create5point1();
|
||||||
|
if (numChannels == 7) return AudioChannelSet::create7point0();
|
||||||
|
if (numChannels == 8) return AudioChannelSet::create7point1();
|
||||||
|
|
||||||
|
return discreteChannels (numChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels)
|
||||||
|
{
|
||||||
|
if (numChannels == 1) return AudioChannelSet::mono();
|
||||||
|
if (numChannels == 2) return AudioChannelSet::stereo();
|
||||||
|
if (numChannels == 3) return AudioChannelSet::createLCR();
|
||||||
|
if (numChannels == 4) return AudioChannelSet::quadraphonic();
|
||||||
|
if (numChannels == 5) return AudioChannelSet::create5point0();
|
||||||
|
if (numChannels == 6) return AudioChannelSet::create5point1();
|
||||||
|
if (numChannels == 7) return AudioChannelSet::create7point0();
|
||||||
|
if (numChannels == 8) return AudioChannelSet::create7point1();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels)
|
||||||
|
{
|
||||||
|
Array<AudioChannelSet> retval;
|
||||||
|
|
||||||
|
if (numChannels != 0)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::discreteChannels (numChannels));
|
||||||
|
|
||||||
|
if (numChannels == 1)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::mono());
|
||||||
|
}
|
||||||
|
else if (numChannels == 2)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::stereo());
|
||||||
|
}
|
||||||
|
else if (numChannels == 3)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::createLCR());
|
||||||
|
retval.add (AudioChannelSet::createLRS());
|
||||||
|
}
|
||||||
|
else if (numChannels == 4)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::quadraphonic());
|
||||||
|
retval.add (AudioChannelSet::createLCRS());
|
||||||
|
}
|
||||||
|
else if (numChannels == 5)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::create5point0());
|
||||||
|
retval.add (AudioChannelSet::pentagonal());
|
||||||
|
}
|
||||||
|
else if (numChannels == 6)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::create5point1());
|
||||||
|
retval.add (AudioChannelSet::create6point0());
|
||||||
|
retval.add (AudioChannelSet::create6point0Music());
|
||||||
|
retval.add (AudioChannelSet::hexagonal());
|
||||||
|
}
|
||||||
|
else if (numChannels == 7)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::create7point0());
|
||||||
|
retval.add (AudioChannelSet::create7point0SDDS());
|
||||||
|
retval.add (AudioChannelSet::create6point1());
|
||||||
|
retval.add (AudioChannelSet::create6point1Music());
|
||||||
|
}
|
||||||
|
else if (numChannels == 8)
|
||||||
|
{
|
||||||
|
retval.add (AudioChannelSet::create7point1());
|
||||||
|
retval.add (AudioChannelSet::create7point1SDDS());
|
||||||
|
retval.add (AudioChannelSet::octagonal());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto order = getAmbisonicOrderForNumChannels (numChannels);
|
||||||
|
if (order >= 0)
|
||||||
|
retval.add (AudioChannelSet::ambisonic (order));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array<ChannelType>& channelArray)
|
||||||
|
{
|
||||||
|
AudioChannelSet set;
|
||||||
|
|
||||||
|
for (auto ch : channelArray)
|
||||||
|
{
|
||||||
|
jassert (! set.channels[static_cast<int> (ch)]);
|
||||||
|
|
||||||
|
set.addChannel (ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask)
|
||||||
|
{
|
||||||
|
return AudioChannelSet (static_cast<uint32> ((dwChannelMask & ((1 << 18) - 1)) << 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 AudioChannelSet::getWaveChannelMask() const noexcept
|
||||||
|
{
|
||||||
|
if (channels.getHighestBit() > topRearRight)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return (channels.toInteger() >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels)
|
||||||
|
{
|
||||||
|
auto sqrtMinusOne = std::sqrt (static_cast<float> (numChannels)) - 1.0f;
|
||||||
|
auto ambisonicOrder = jmax (0, static_cast<int> (std::floor (sqrtMinusOne)));
|
||||||
|
|
||||||
|
if (ambisonicOrder > 5)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return (static_cast<float> (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
class AudioChannelSetUnitTest : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioChannelSetUnitTest() : UnitTest ("AudioChannelSetUnitTest", "Audio") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
auto max = AudioChannelSet::maxChannelsOfNamedLayout;
|
||||||
|
|
||||||
|
beginTest ("maxChannelsOfNamedLayout is non-discrete");
|
||||||
|
expect (AudioChannelSet::channelSetsWithNumberOfChannels (max).size() >= 2);
|
||||||
|
|
||||||
|
beginTest ("channelSetsWithNumberOfChannels returns correct speaker count");
|
||||||
|
{
|
||||||
|
for (auto ch = 1; ch <= max; ++ch)
|
||||||
|
{
|
||||||
|
auto channelSets = AudioChannelSet::channelSetsWithNumberOfChannels (ch);
|
||||||
|
|
||||||
|
for (auto set : channelSets)
|
||||||
|
expect (set.size() == ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("Ambisonics");
|
||||||
|
{
|
||||||
|
uint64 mask = 0;
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN0);
|
||||||
|
checkAmbisonic (mask, 0, "0th Order Ambisonics");
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN1) | (1ull << AudioChannelSet::ambisonicACN2) | (1ull << AudioChannelSet::ambisonicACN3);
|
||||||
|
checkAmbisonic (mask, 1, "1st Order Ambisonics");
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN4) | (1ull << AudioChannelSet::ambisonicACN5) | (1ull << AudioChannelSet::ambisonicACN6)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN7) | (1ull << AudioChannelSet::ambisonicACN8);
|
||||||
|
checkAmbisonic (mask, 2, "2nd Order Ambisonics");
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN9) | (1ull << AudioChannelSet::ambisonicACN10) | (1ull << AudioChannelSet::ambisonicACN11)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN12) | (1ull << AudioChannelSet::ambisonicACN13) | (1ull << AudioChannelSet::ambisonicACN14)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN15);
|
||||||
|
checkAmbisonic (mask, 3, "3rd Order Ambisonics");
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN16) | (1ull << AudioChannelSet::ambisonicACN17) | (1ull << AudioChannelSet::ambisonicACN18)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN19) | (1ull << AudioChannelSet::ambisonicACN20) | (1ull << AudioChannelSet::ambisonicACN21)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN22) | (1ull << AudioChannelSet::ambisonicACN23) | (1ull << AudioChannelSet::ambisonicACN24);
|
||||||
|
checkAmbisonic (mask, 4, "4th Order Ambisonics");
|
||||||
|
|
||||||
|
mask |= (1ull << AudioChannelSet::ambisonicACN25) | (1ull << AudioChannelSet::ambisonicACN26) | (1ull << AudioChannelSet::ambisonicACN27)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN28) | (1ull << AudioChannelSet::ambisonicACN29) | (1ull << AudioChannelSet::ambisonicACN30)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN31) | (1ull << AudioChannelSet::ambisonicACN32) | (1ull << AudioChannelSet::ambisonicACN33)
|
||||||
|
| (1ull << AudioChannelSet::ambisonicACN34) | (1ull << AudioChannelSet::ambisonicACN35);
|
||||||
|
checkAmbisonic (mask, 5, "5th Order Ambisonics");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkAmbisonic (uint64 mask, int order, const char* layoutName)
|
||||||
|
{
|
||||||
|
auto expected = AudioChannelSet::ambisonic (order);
|
||||||
|
auto numChannels = expected.size();
|
||||||
|
|
||||||
|
expect (numChannels == BigInteger ((int64) mask).countNumberOfSetBits());
|
||||||
|
expect (channelSetFromMask (mask) == expected);
|
||||||
|
|
||||||
|
expect (order == expected.getAmbisonicOrder());
|
||||||
|
expect (expected.getDescription() == layoutName);
|
||||||
|
|
||||||
|
auto layouts = AudioChannelSet::channelSetsWithNumberOfChannels (numChannels);
|
||||||
|
expect (layouts.contains (expected));
|
||||||
|
|
||||||
|
for (auto layout : layouts)
|
||||||
|
expect (layout.getAmbisonicOrder() == (layout == expected ? order : -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static AudioChannelSet channelSetFromMask (uint64 mask)
|
||||||
|
{
|
||||||
|
Array<AudioChannelSet::ChannelType> channels;
|
||||||
|
for (int bit = 0; bit <= 62; ++bit)
|
||||||
|
if ((mask & (1ull << bit)) != 0)
|
||||||
|
channels.add (static_cast<AudioChannelSet::ChannelType> (bit));
|
||||||
|
|
||||||
|
return AudioChannelSet::channelSetWithChannels (channels);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static AudioChannelSetUnitTest audioChannelSetUnitTest;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace juce
|
470
modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
Normal file
470
modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
Normal file
|
@ -0,0 +1,470 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Represents a set of audio channel types.
|
||||||
|
|
||||||
|
For example, you might have a set of left + right channels, which is a stereo
|
||||||
|
channel set. It is a collection of values from the AudioChannelSet::ChannelType
|
||||||
|
enum, where each type may only occur once within the set.
|
||||||
|
|
||||||
|
The documentation below lists which AudioChannelSet corresponds to which native
|
||||||
|
layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio
|
||||||
|
are particularly confusing. For example, the layout which is labeled as "7.1 SDDS"
|
||||||
|
in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas
|
||||||
|
AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's
|
||||||
|
kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag
|
||||||
|
as an indication to the actual layout of the speakers.
|
||||||
|
|
||||||
|
@see Bus
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioChannelSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Creates an empty channel set.
|
||||||
|
You can call addChannel to add channels to the set.
|
||||||
|
*/
|
||||||
|
AudioChannelSet() noexcept {}
|
||||||
|
|
||||||
|
/** Creates a zero-channel set which can be used to indicate that a
|
||||||
|
bus is disabled. */
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE disabled();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a one-channel mono set (centre).
|
||||||
|
|
||||||
|
Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE mono();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set containing a stereo set (left, right).
|
||||||
|
|
||||||
|
Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE stereo();
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a set containing an LCR set (left, right, centre).
|
||||||
|
|
||||||
|
Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "LRC" in Cubase.
|
||||||
|
This format is referred to as "LCR" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE createLCR();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set containing an LRS set (left, right, surround).
|
||||||
|
|
||||||
|
Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "LRS" in Cubase.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE createLRS();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set containing an LCRS set (left, right, centre, surround).
|
||||||
|
|
||||||
|
Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "LCRS (Pro Logic)" in Logic Pro.
|
||||||
|
This format is referred to as "LRCS" in Cubase.
|
||||||
|
This format is referred to as "LCRS" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE createLCRS();
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
|
||||||
|
|
||||||
|
Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "5.0" in Cubase.
|
||||||
|
This format is referred to as "5.0" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create5point0();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE).
|
||||||
|
|
||||||
|
Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "5.1 (ITU 775)" in Logic Pro.
|
||||||
|
This format is referred to as "5.1" in Cubase.
|
||||||
|
This format is referred to as "5.1" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create5point1();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround).
|
||||||
|
|
||||||
|
Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio)
|
||||||
|
|
||||||
|
Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)".
|
||||||
|
This format is referred to as "6.0 Cine" in Cubase.
|
||||||
|
This format is referred to as "6.0" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create6point0();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE).
|
||||||
|
|
||||||
|
Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "6.1" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create6point1();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide).
|
||||||
|
|
||||||
|
Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "6.0 Music" in Cubase.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create6point0Music();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE).
|
||||||
|
|
||||||
|
Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create6point1Music();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear).
|
||||||
|
|
||||||
|
Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "7.0" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point0();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre).
|
||||||
|
|
||||||
|
Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "7.0 SDDS" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE).
|
||||||
|
|
||||||
|
Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "7.1 (3/4.1)" in Logic Pro.
|
||||||
|
This format is referred to as "7.1" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point1();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE).
|
||||||
|
|
||||||
|
Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "7.1 (SDDS)" in Logic Pro.
|
||||||
|
This format is referred to as "7.1 SDDS" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS();
|
||||||
|
|
||||||
|
/** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight).
|
||||||
|
|
||||||
|
Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point0point2();
|
||||||
|
|
||||||
|
/** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight).
|
||||||
|
|
||||||
|
Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), n/a (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE create7point1point2();
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround)
|
||||||
|
|
||||||
|
Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio)
|
||||||
|
|
||||||
|
This format is referred to as "Quadraphonic" in Logic Pro.
|
||||||
|
This format is referred to as "Quadro" in Cubase.
|
||||||
|
This format is referred to as "Quad" in Pro Tools.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE quadraphonic();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear).
|
||||||
|
|
||||||
|
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE pentagonal();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre).
|
||||||
|
|
||||||
|
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE hexagonal();
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight).
|
||||||
|
|
||||||
|
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE octagonal();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order.
|
||||||
|
|
||||||
|
Is equivalent to: kAmbiXXXOrderACN (VST), AAX_eStemFormat_Ambi_XXX_ACN (AAX), kAudioChannelLayoutTag_HOA_ACN_SN3D (CoreAudio)
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE ambisonic (int order = 1);
|
||||||
|
|
||||||
|
/** Returns the order of the ambisonic layout represented by this AudioChannelSet. If the
|
||||||
|
AudioChannelSet is not an ambisonic layout, then this method will return -1.
|
||||||
|
*/
|
||||||
|
int getAmbisonicOrder() const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a set of untyped discrete channels. */
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels);
|
||||||
|
|
||||||
|
/** Create a canonical channel set for a given number of channels.
|
||||||
|
For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels);
|
||||||
|
|
||||||
|
/** Create a channel set for a given number of channels which is non-discrete.
|
||||||
|
If numChannels is larger than the number of channels of the surround format
|
||||||
|
with the maximum amount of channels (currently 7.1 Surround), then this
|
||||||
|
function returns an empty set.*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels);
|
||||||
|
|
||||||
|
/** Return an array of channel sets which have a given number of channels */
|
||||||
|
static Array<AudioChannelSet> JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Represents different audio channel types. */
|
||||||
|
enum ChannelType
|
||||||
|
{
|
||||||
|
unknown = 0, /**< Unknown channel type. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
left = 1, /**< L channel. */
|
||||||
|
right = 2, /**< R channel. */
|
||||||
|
centre = 3, /**< C channel. (Sometimes M for mono) */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
LFE = 4, /**< LFE channel. */
|
||||||
|
leftSurround = 5, /**< Ls channel. */
|
||||||
|
rightSurround = 6, /**< Rs channel. */
|
||||||
|
leftCentre = 7, /**< Lc (AAX/VST), Lc used as Lss in AU for most layouts. */
|
||||||
|
rightCentre = 8, /**< Rc (AAX/VST), Rc used as Rss in AU for most layouts. */
|
||||||
|
centreSurround = 9, /**< Cs/S channel. */
|
||||||
|
surround = centreSurround, /**< Same as Centre Surround channel. */
|
||||||
|
leftSurroundSide = 10, /**< Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) channel. */
|
||||||
|
rightSurroundSide = 11, /**< Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) channel. */
|
||||||
|
topMiddle = 12, /**< Top Middle channel. */
|
||||||
|
topFrontLeft = 13, /**< Top Front Left channel. */
|
||||||
|
topFrontCentre = 14, /**< Top Front Centre channel. */
|
||||||
|
topFrontRight = 15, /**< Top Front Right channel. */
|
||||||
|
topRearLeft = 16, /**< Top Rear Left channel. */
|
||||||
|
topRearCentre = 17, /**< Top Rear Centre channel. */
|
||||||
|
topRearRight = 18, /**< Top Rear Right channel. */
|
||||||
|
LFE2 = 19, /**< Second LFE channel. */
|
||||||
|
leftSurroundRear = 20, /**< Lsr (AAX), Lcs (VST), Rls (AU) channel. */
|
||||||
|
rightSurroundRear = 21, /**< Rsr (AAX), Rcs (VST), Rrs (AU) channel. */
|
||||||
|
wideLeft = 22, /**< Wide Left channel. */
|
||||||
|
wideRight = 23, /**< Wide Right channel. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Used by Dolby Atmos 7.0.2 and 7.1.2
|
||||||
|
topSideLeft = 28, /**< Lts (AAX), Tsl (VST) channel for Dolby Atmos. */
|
||||||
|
topSideRight = 29, /**< Rts (AAX), Tsr (VST) channel for Dolby Atmos. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Ambisonic ACN formats - all channels are SN3D normalised
|
||||||
|
|
||||||
|
// zero-th and first-order ambisonic ACN
|
||||||
|
ambisonicACN0 = 24, /**< Zero-th ambisonic channel number 0. */
|
||||||
|
ambisonicACN1 = 25, /**< First-order ambisonic channel number 1. */
|
||||||
|
ambisonicACN2 = 26, /**< First-order ambisonic channel number 2. */
|
||||||
|
ambisonicACN3 = 27, /**< First-order ambisonic channel number 3. */
|
||||||
|
|
||||||
|
// second-order ambisonic
|
||||||
|
ambisonicACN4 = 30, /**< Second-order ambisonic channel number 4. */
|
||||||
|
ambisonicACN5 = 31, /**< Second-order ambisonic channel number 5. */
|
||||||
|
ambisonicACN6 = 32, /**< Second-order ambisonic channel number 6. */
|
||||||
|
ambisonicACN7 = 33, /**< Second-order ambisonic channel number 7. */
|
||||||
|
ambisonicACN8 = 34, /**< Second-order ambisonic channel number 8. */
|
||||||
|
|
||||||
|
// third-order ambisonic
|
||||||
|
ambisonicACN9 = 35, /**< Third-order ambisonic channel number 9. */
|
||||||
|
ambisonicACN10 = 36, /**< Third-order ambisonic channel number 10. */
|
||||||
|
ambisonicACN11 = 37, /**< Third-order ambisonic channel number 11. */
|
||||||
|
ambisonicACN12 = 38, /**< Third-order ambisonic channel number 12. */
|
||||||
|
ambisonicACN13 = 39, /**< Third-order ambisonic channel number 13. */
|
||||||
|
ambisonicACN14 = 40, /**< Third-order ambisonic channel number 14. */
|
||||||
|
ambisonicACN15 = 41, /**< Third-order ambisonic channel number 15. */
|
||||||
|
|
||||||
|
// fourth-order ambisonic
|
||||||
|
ambisonicACN16 = 42, /**< Fourth-order ambisonic channel number 16. */
|
||||||
|
ambisonicACN17 = 43, /**< Fourth-order ambisonic channel number 17. */
|
||||||
|
ambisonicACN18 = 44, /**< Fourth-order ambisonic channel number 18. */
|
||||||
|
ambisonicACN19 = 45, /**< Fourth-order ambisonic channel number 19. */
|
||||||
|
ambisonicACN20 = 46, /**< Fourth-order ambisonic channel number 20. */
|
||||||
|
ambisonicACN21 = 47, /**< Fourth-order ambisonic channel number 21. */
|
||||||
|
ambisonicACN22 = 48, /**< Fourth-order ambisonic channel number 22. */
|
||||||
|
ambisonicACN23 = 49, /**< Fourth-order ambisonic channel number 23. */
|
||||||
|
ambisonicACN24 = 50, /**< Fourth-order ambisonic channel number 24. */
|
||||||
|
|
||||||
|
// fifth-order ambisonic
|
||||||
|
ambisonicACN25 = 51, /**< Fifth-order ambisonic channel number 25. */
|
||||||
|
ambisonicACN26 = 52, /**< Fifth-order ambisonic channel number 26. */
|
||||||
|
ambisonicACN27 = 53, /**< Fifth-order ambisonic channel number 27. */
|
||||||
|
ambisonicACN28 = 54, /**< Fifth-order ambisonic channel number 28. */
|
||||||
|
ambisonicACN29 = 55, /**< Fifth-order ambisonic channel number 29. */
|
||||||
|
ambisonicACN30 = 56, /**< Fifth-order ambisonic channel number 30. */
|
||||||
|
ambisonicACN31 = 57, /**< Fifth-order ambisonic channel number 31. */
|
||||||
|
ambisonicACN32 = 58, /**< Fifth-order ambisonic channel number 32. */
|
||||||
|
ambisonicACN33 = 59, /**< Fifth-order ambisonic channel number 33. */
|
||||||
|
ambisonicACN34 = 60, /**< Fifth-order ambisonic channel number 34. */
|
||||||
|
ambisonicACN35 = 61, /**< Fifth-order ambisonic channel number 35. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
ambisonicW = ambisonicACN0, /**< Same as zero-th ambisonic channel number 0. */
|
||||||
|
ambisonicX = ambisonicACN3, /**< Same as first-order ambisonic channel number 3. */
|
||||||
|
ambisonicY = ambisonicACN1, /**< Same as first-order ambisonic channel number 1. */
|
||||||
|
ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
discreteChannel0 = 64 /**< 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". */
|
||||||
|
static String JUCE_CALLTYPE getChannelTypeName (ChannelType);
|
||||||
|
|
||||||
|
/** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */
|
||||||
|
static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType);
|
||||||
|
|
||||||
|
/** Returns the channel type from an abbreviated name. */
|
||||||
|
static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
maxChannelsOfNamedLayout = 36
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Adds a channel to the set. */
|
||||||
|
void addChannel (ChannelType newChannelType);
|
||||||
|
|
||||||
|
/** Removes a channel from the set. */
|
||||||
|
void removeChannel (ChannelType newChannelType);
|
||||||
|
|
||||||
|
/** Returns the number of channels in the set. */
|
||||||
|
int size() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if there are no channels in the set. */
|
||||||
|
bool isDisabled() const noexcept { return size() == 0; }
|
||||||
|
|
||||||
|
/** Returns an array of all the types in this channel set. */
|
||||||
|
Array<ChannelType> getChannelTypes() const;
|
||||||
|
|
||||||
|
/** Returns the type of one of the channels in the set, by index. */
|
||||||
|
ChannelType getTypeOfChannel (int channelIndex) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the index for a particular channel-type.
|
||||||
|
Will return -1 if the this set does not contain a channel of this type. */
|
||||||
|
int getChannelIndexForType (ChannelType type) const noexcept;
|
||||||
|
|
||||||
|
/** Returns a string containing a whitespace-separated list of speaker types
|
||||||
|
corresponding to each channel. For example in a 5.1 arrangement,
|
||||||
|
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown,
|
||||||
|
the returned string will be empty.*/
|
||||||
|
String getSpeakerArrangementAsString() const;
|
||||||
|
|
||||||
|
/** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString
|
||||||
|
|
||||||
|
@see getSpeakerArrangementAsString */
|
||||||
|
static AudioChannelSet fromAbbreviatedString (const String& set);
|
||||||
|
|
||||||
|
/** Returns the description of the current layout. For example, this method may return
|
||||||
|
"Quadraphonic". Note that the returned string may not be unique. */
|
||||||
|
String getDescription() const;
|
||||||
|
|
||||||
|
/** Returns if this is a channel layout made-up of discrete channels. */
|
||||||
|
bool isDiscreteLayout() const noexcept;
|
||||||
|
|
||||||
|
/** Intersect two channel layouts. */
|
||||||
|
void intersect (const AudioChannelSet& other) { channels &= other.channels; }
|
||||||
|
|
||||||
|
/** Creates a channel set for a list of channel types. This function will assert
|
||||||
|
if you supply a duplicate channel.
|
||||||
|
|
||||||
|
Note that this method ignores the order in which the channels are given, i.e.
|
||||||
|
two arrays with the same elements but in a different order will still result
|
||||||
|
in the same channel set.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array<ChannelType>&);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Conversion between wave and juce channel layout identifiers
|
||||||
|
|
||||||
|
/** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used
|
||||||
|
in .wav files). */
|
||||||
|
static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask);
|
||||||
|
|
||||||
|
/** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav
|
||||||
|
files) of the receiver.
|
||||||
|
|
||||||
|
Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask
|
||||||
|
representation.
|
||||||
|
*/
|
||||||
|
int32 getWaveChannelMask() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool operator== (const AudioChannelSet&) const noexcept;
|
||||||
|
bool operator!= (const AudioChannelSet&) const noexcept;
|
||||||
|
bool operator< (const AudioChannelSet&) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
BigInteger channels;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
explicit AudioChannelSet (uint32);
|
||||||
|
explicit AudioChannelSet (const Array<ChannelType>&);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
603
modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp
Normal file
603
modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp
Normal file
|
@ -0,0 +1,603 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fffff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fffff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fffffff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
const double maxVal = (double) 0x7fffffff;
|
||||||
|
char* intData = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
intData += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += destBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= destBytesPerSample;
|
||||||
|
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
|
||||||
|
|
||||||
|
char* d = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(float*) d = source[i];
|
||||||
|
|
||||||
|
#if JUCE_BIG_ENDIAN
|
||||||
|
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
d += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||||
|
{
|
||||||
|
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
|
||||||
|
|
||||||
|
char* d = static_cast<char*> (dest);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
*(float*) d = source[i];
|
||||||
|
|
||||||
|
#if JUCE_LITTLE_ENDIAN
|
||||||
|
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
d += destBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const float scale = 1.0f / 0x7fff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const float scale = 1.0f / 0x7fff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const float scale = 1.0f / 0x7fffff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const float scale = 1.0f / 0x7fffff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const auto scale = 1.0f / (float) 0x7fffffff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const auto scale = 1.0f / (float) 0x7fffffff;
|
||||||
|
const char* intData = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
|
||||||
|
intData += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intData += srcBytesPerSample * numSamples;
|
||||||
|
|
||||||
|
for (int i = numSamples; --i >= 0;)
|
||||||
|
{
|
||||||
|
intData -= srcBytesPerSample;
|
||||||
|
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const char* s = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = *(float*)s;
|
||||||
|
|
||||||
|
#if JUCE_BIG_ENDIAN
|
||||||
|
uint32* const d = (uint32*) (dest + i);
|
||||||
|
*d = ByteOrder::swap (*d);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
s += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||||
|
{
|
||||||
|
const char* s = static_cast<const char*> (source);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
dest[i] = *(float*)s;
|
||||||
|
|
||||||
|
#if JUCE_LITTLE_ENDIAN
|
||||||
|
uint32* const d = (uint32*) (dest + i);
|
||||||
|
*d = ByteOrder::swap (*d);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
s += srcBytesPerSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat,
|
||||||
|
const float* const source,
|
||||||
|
void* const dest,
|
||||||
|
const int numSamples)
|
||||||
|
{
|
||||||
|
switch (destFormat)
|
||||||
|
{
|
||||||
|
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break;
|
||||||
|
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break;
|
||||||
|
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break;
|
||||||
|
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break;
|
||||||
|
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break;
|
||||||
|
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break;
|
||||||
|
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break;
|
||||||
|
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break;
|
||||||
|
default: jassertfalse; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat,
|
||||||
|
const void* const source,
|
||||||
|
float* const dest,
|
||||||
|
const int numSamples)
|
||||||
|
{
|
||||||
|
switch (sourceFormat)
|
||||||
|
{
|
||||||
|
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break;
|
||||||
|
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break;
|
||||||
|
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break;
|
||||||
|
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break;
|
||||||
|
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break;
|
||||||
|
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break;
|
||||||
|
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break;
|
||||||
|
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break;
|
||||||
|
default: jassertfalse; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void AudioDataConverters::interleaveSamples (const float** const source,
|
||||||
|
float* const dest,
|
||||||
|
const int numSamples,
|
||||||
|
const int numChannels)
|
||||||
|
{
|
||||||
|
for (int chan = 0; chan < numChannels; ++chan)
|
||||||
|
{
|
||||||
|
int i = chan;
|
||||||
|
const float* src = source [chan];
|
||||||
|
|
||||||
|
for (int j = 0; j < numSamples; ++j)
|
||||||
|
{
|
||||||
|
dest [i] = src [j];
|
||||||
|
i += numChannels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDataConverters::deinterleaveSamples (const float* const source,
|
||||||
|
float** const dest,
|
||||||
|
const int numSamples,
|
||||||
|
const int numChannels)
|
||||||
|
{
|
||||||
|
for (int chan = 0; chan < numChannels; ++chan)
|
||||||
|
{
|
||||||
|
int i = chan;
|
||||||
|
float* dst = dest [chan];
|
||||||
|
|
||||||
|
for (int j = 0; j < numSamples; ++j)
|
||||||
|
{
|
||||||
|
dst [j] = source [i];
|
||||||
|
i += numChannels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class AudioConversionTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioConversionTests() : UnitTest ("Audio data conversion", "Audio") {}
|
||||||
|
|
||||||
|
template <class F1, class E1, class F2, class E2>
|
||||||
|
struct Test5
|
||||||
|
{
|
||||||
|
static void test (UnitTest& unitTest, Random& r)
|
||||||
|
{
|
||||||
|
test (unitTest, false, r);
|
||||||
|
test (unitTest, true, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test (UnitTest& unitTest, bool inPlace, Random& r)
|
||||||
|
{
|
||||||
|
const int numSamples = 2048;
|
||||||
|
int32 original [numSamples], converted [numSamples], reversed [numSamples];
|
||||||
|
|
||||||
|
{
|
||||||
|
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original);
|
||||||
|
bool clippingFailed = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples / 2; ++i)
|
||||||
|
{
|
||||||
|
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f);
|
||||||
|
|
||||||
|
if (! d.isFloatingPoint())
|
||||||
|
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed;
|
||||||
|
|
||||||
|
++d;
|
||||||
|
d.setAsInt32 (r.nextInt());
|
||||||
|
++d;
|
||||||
|
}
|
||||||
|
|
||||||
|
unitTest.expect (! clippingFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert data from the source to dest format..
|
||||||
|
std::unique_ptr<AudioData::Converter> conv (new AudioData::ConverterInstance <AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>,
|
||||||
|
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst>>());
|
||||||
|
conv->convertSamples (inPlace ? reversed : converted, original, numSamples);
|
||||||
|
|
||||||
|
// ..and back again..
|
||||||
|
conv.reset (new AudioData::ConverterInstance <AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>,
|
||||||
|
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst>>());
|
||||||
|
if (! inPlace)
|
||||||
|
zeromem (reversed, sizeof (reversed));
|
||||||
|
|
||||||
|
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples);
|
||||||
|
|
||||||
|
{
|
||||||
|
int biggestDiff = 0;
|
||||||
|
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original);
|
||||||
|
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed);
|
||||||
|
|
||||||
|
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution()
|
||||||
|
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution();
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32()));
|
||||||
|
++d1;
|
||||||
|
++d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
unitTest.expect (biggestDiff <= errorMargin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class F1, class E1, class FormatType>
|
||||||
|
struct Test3
|
||||||
|
{
|
||||||
|
static void test (UnitTest& unitTest, Random& r)
|
||||||
|
{
|
||||||
|
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest, r);
|
||||||
|
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class FormatType, class Endianness>
|
||||||
|
struct Test2
|
||||||
|
{
|
||||||
|
static void test (UnitTest& unitTest, Random& r)
|
||||||
|
{
|
||||||
|
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest, r);
|
||||||
|
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest, r);
|
||||||
|
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest, r);
|
||||||
|
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest, r);
|
||||||
|
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest, r);
|
||||||
|
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class FormatType>
|
||||||
|
struct Test1
|
||||||
|
{
|
||||||
|
static void test (UnitTest& unitTest, Random& r)
|
||||||
|
{
|
||||||
|
Test2 <FormatType, AudioData::BigEndian>::test (unitTest, r);
|
||||||
|
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
Random r = getRandom();
|
||||||
|
beginTest ("Round-trip conversion: Int8");
|
||||||
|
Test1 <AudioData::Int8>::test (*this, r);
|
||||||
|
beginTest ("Round-trip conversion: Int16");
|
||||||
|
Test1 <AudioData::Int16>::test (*this, r);
|
||||||
|
beginTest ("Round-trip conversion: Int24");
|
||||||
|
Test1 <AudioData::Int24>::test (*this, r);
|
||||||
|
beginTest ("Round-trip conversion: Int32");
|
||||||
|
Test1 <AudioData::Int32>::test (*this, r);
|
||||||
|
beginTest ("Round-trip conversion: Float32");
|
||||||
|
Test1 <AudioData::Float32>::test (*this, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static AudioConversionTests audioConversionUnitTests;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace juce
|
716
modules/juce_audio_basics/buffers/juce_AudioDataConverters.h
Normal file
716
modules/juce_audio_basics/buffers/juce_AudioDataConverters.h
Normal file
|
@ -0,0 +1,716 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class a container which holds all the classes pertaining to the AudioData::Pointer
|
||||||
|
audio sample format class.
|
||||||
|
|
||||||
|
@see AudioData::Pointer.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
// These types can be used as the SampleFormat template parameter for the AudioData::Pointer class.
|
||||||
|
|
||||||
|
class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */
|
||||||
|
class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */
|
||||||
|
class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */
|
||||||
|
class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */
|
||||||
|
class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */
|
||||||
|
class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// These types can be used as the Endianness template parameter for the AudioData::Pointer class.
|
||||||
|
|
||||||
|
class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */
|
||||||
|
class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */
|
||||||
|
class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// These types can be used as the InterleavingType template parameter for the AudioData::Pointer class.
|
||||||
|
|
||||||
|
class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */
|
||||||
|
class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// These types can be used as the Constness template parameter for the AudioData::Pointer class.
|
||||||
|
|
||||||
|
class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */
|
||||||
|
class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */
|
||||||
|
|
||||||
|
#ifndef DOXYGEN
|
||||||
|
//==============================================================================
|
||||||
|
class BigEndian
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); }
|
||||||
|
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); }
|
||||||
|
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); }
|
||||||
|
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); }
|
||||||
|
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); }
|
||||||
|
enum { isBigEndian = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class LittleEndian
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); }
|
||||||
|
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); }
|
||||||
|
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); }
|
||||||
|
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); }
|
||||||
|
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); }
|
||||||
|
enum { isBigEndian = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
#if JUCE_BIG_ENDIAN
|
||||||
|
class NativeEndian : public BigEndian {};
|
||||||
|
#else
|
||||||
|
class NativeEndian : public LittleEndian {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class Int8
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Int8 (void* d) noexcept : data (static_cast<int8*> (d)) {}
|
||||||
|
|
||||||
|
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 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 setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); }
|
||||||
|
inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); }
|
||||||
|
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); }
|
||||||
|
inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); }
|
||||||
|
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); }
|
||||||
|
inline void clear() noexcept { *data = 0; }
|
||||||
|
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
int8* data;
|
||||||
|
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class UInt8
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline UInt8 (void* d) noexcept : data (static_cast<uint8*> (d)) {}
|
||||||
|
|
||||||
|
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 getAsFloatBE() const noexcept { return getAsFloatLE(); }
|
||||||
|
inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + 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(); }
|
||||||
|
inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); }
|
||||||
|
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); }
|
||||||
|
inline void clear() noexcept { *data = 128; }
|
||||||
|
inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
uint8* data;
|
||||||
|
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Int16
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Int16 (void* d) noexcept : data (static_cast<uint16*> (d)) {}
|
||||||
|
|
||||||
|
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 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)); }
|
||||||
|
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); }
|
||||||
|
inline void clear() noexcept { *data = 0; }
|
||||||
|
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
uint16* data;
|
||||||
|
enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Int24
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Int24 (void* d) noexcept : data (static_cast<char*> (d)) {}
|
||||||
|
|
||||||
|
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 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); }
|
||||||
|
inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); }
|
||||||
|
inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; }
|
||||||
|
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; }
|
||||||
|
|
||||||
|
char* data;
|
||||||
|
enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Int32
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Int32 (void* d) noexcept : data (static_cast<uint32*> (d)) {}
|
||||||
|
|
||||||
|
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 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); }
|
||||||
|
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); }
|
||||||
|
inline void clear() noexcept { *data = 0; }
|
||||||
|
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
uint32* data;
|
||||||
|
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A 32-bit integer type, of which only the bottom 24 bits are used. */
|
||||||
|
class Int24in32 : public Int32
|
||||||
|
{
|
||||||
|
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 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); }
|
||||||
|
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); }
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
|
||||||
|
inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Float32
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {}
|
||||||
|
|
||||||
|
inline void advance() noexcept { ++data; }
|
||||||
|
inline void skip (int numSamples) noexcept { data += numSamples; }
|
||||||
|
#if JUCE_BIG_ENDIAN
|
||||||
|
inline float getAsFloatBE() const noexcept { return *data; }
|
||||||
|
inline void setAsFloatBE (float newValue) noexcept { *data = newValue; }
|
||||||
|
inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; }
|
||||||
|
inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); }
|
||||||
|
#else
|
||||||
|
inline float getAsFloatLE() const noexcept { return *data; }
|
||||||
|
inline void setAsFloatLE (float newValue) noexcept { *data = newValue; }
|
||||||
|
inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; }
|
||||||
|
inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); }
|
||||||
|
#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 clear() noexcept { *data = 0; }
|
||||||
|
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
|
||||||
|
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); }
|
||||||
|
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); }
|
||||||
|
inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; }
|
||||||
|
|
||||||
|
float* data;
|
||||||
|
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class NonInterleaved
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline NonInterleaved() noexcept {}
|
||||||
|
inline NonInterleaved (const NonInterleaved&) noexcept {}
|
||||||
|
inline NonInterleaved (const int) noexcept {}
|
||||||
|
inline void copyFrom (const NonInterleaved&) noexcept {}
|
||||||
|
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); }
|
||||||
|
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); }
|
||||||
|
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); }
|
||||||
|
template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; }
|
||||||
|
|
||||||
|
enum { isInterleavedType = 0, numInterleavedChannels = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Interleaved
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline Interleaved() noexcept : numInterleavedChannels (1) {}
|
||||||
|
inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {}
|
||||||
|
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {}
|
||||||
|
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; }
|
||||||
|
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); }
|
||||||
|
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); }
|
||||||
|
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } }
|
||||||
|
template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; }
|
||||||
|
int numInterleavedChannels;
|
||||||
|
enum { isInterleavedType = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class NonConst
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using VoidType = void;
|
||||||
|
static inline void* toVoidPtr (VoidType* v) noexcept { return v; }
|
||||||
|
enum { isConst = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Const
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using VoidType = const void;
|
||||||
|
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); }
|
||||||
|
enum { isConst = 1 };
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A pointer to a block of audio data with a particular encoding.
|
||||||
|
|
||||||
|
This object can be used to read and write from blocks of encoded audio samples. To create one, you specify
|
||||||
|
the audio format as a series of template parameters, e.g.
|
||||||
|
@code
|
||||||
|
// this creates a pointer for reading from a const array of 16-bit little-endian packed samples.
|
||||||
|
AudioData::Pointer <AudioData::Int16,
|
||||||
|
AudioData::LittleEndian,
|
||||||
|
AudioData::NonInterleaved,
|
||||||
|
AudioData::Const> pointer (someRawAudioData);
|
||||||
|
|
||||||
|
// These methods read the sample that is being pointed to
|
||||||
|
float firstSampleAsFloat = pointer.getAsFloat();
|
||||||
|
int32 firstSampleAsInt = pointer.getAsInt32();
|
||||||
|
++pointer; // moves the pointer to the next sample.
|
||||||
|
pointer += 3; // skips the next 3 samples.
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
The convertSamples() method lets you copy a range of samples from one format to another, automatically
|
||||||
|
converting its format.
|
||||||
|
|
||||||
|
@see AudioData::Converter
|
||||||
|
*/
|
||||||
|
template <typename SampleFormat,
|
||||||
|
typename Endianness,
|
||||||
|
typename InterleavingType,
|
||||||
|
typename Constness>
|
||||||
|
class Pointer : private InterleavingType // (inherited for EBCO)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a non-interleaved pointer from some raw data in the appropriate format.
|
||||||
|
This constructor is only used if you've specified the AudioData::NonInterleaved option -
|
||||||
|
for interleaved formats, use the constructor that also takes a number of channels.
|
||||||
|
*/
|
||||||
|
Pointer (typename Constness::VoidType* sourceData) noexcept
|
||||||
|
: data (Constness::toVoidPtr (sourceData))
|
||||||
|
{
|
||||||
|
// If you're using interleaved data, call the other constructor! If you're using non-interleaved data,
|
||||||
|
// you should pass NonInterleaved as the template parameter for the interleaving type!
|
||||||
|
static_assert (InterleavingType::isInterleavedType == 0, "Incorrect constructor for interleaved data");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels.
|
||||||
|
For non-interleaved data, use the other constructor.
|
||||||
|
*/
|
||||||
|
Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept
|
||||||
|
: InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a copy of another pointer. */
|
||||||
|
Pointer (const Pointer& other) noexcept
|
||||||
|
: InterleavingType (other), data (other.data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer& operator= (const Pointer& other) noexcept
|
||||||
|
{
|
||||||
|
InterleavingType::operator= (other);
|
||||||
|
data = other.data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the value of the first sample as a floating point value.
|
||||||
|
The value will be in the range -1.0 to 1.0 for integer formats. For floating point
|
||||||
|
formats, the value could be outside that range, although -1 to 1 is the standard range.
|
||||||
|
*/
|
||||||
|
inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); }
|
||||||
|
|
||||||
|
/** Sets the value of the first sample as a floating point value.
|
||||||
|
|
||||||
|
(This method can only be used if the AudioData::NonConst option was used).
|
||||||
|
The value should be in the range -1.0 to 1.0 - for integer formats, values outside that
|
||||||
|
range will be clipped. For floating point formats, any value passed in here will be
|
||||||
|
written directly, although -1 to 1 is the standard range.
|
||||||
|
*/
|
||||||
|
inline void setAsFloat (float newValue) noexcept
|
||||||
|
{
|
||||||
|
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
|
||||||
|
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer");
|
||||||
|
Endianness::setAsFloat (data, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the value of the first sample as a 32-bit integer.
|
||||||
|
The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be
|
||||||
|
shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up
|
||||||
|
by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will
|
||||||
|
be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff.
|
||||||
|
*/
|
||||||
|
inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); }
|
||||||
|
|
||||||
|
/** Sets the value of the first sample as a 32-bit integer.
|
||||||
|
This will be mapped to the range of the format that is being written - see getAsInt32().
|
||||||
|
*/
|
||||||
|
inline void setAsInt32 (int32 newValue) noexcept
|
||||||
|
{
|
||||||
|
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
|
||||||
|
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer");
|
||||||
|
Endianness::setAsInt32 (data, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Moves the pointer along to the next sample. */
|
||||||
|
inline Pointer& operator++() noexcept { advance(); return *this; }
|
||||||
|
|
||||||
|
/** Moves the pointer back to the previous sample. */
|
||||||
|
inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; }
|
||||||
|
|
||||||
|
/** Adds a number of samples to the pointer's position. */
|
||||||
|
Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; }
|
||||||
|
|
||||||
|
/** Writes a stream of samples into this pointer from another pointer.
|
||||||
|
This will copy the specified number of samples, converting between formats appropriately.
|
||||||
|
*/
|
||||||
|
void convertSamples (Pointer source, int numSamples) const noexcept
|
||||||
|
{
|
||||||
|
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
|
||||||
|
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer");
|
||||||
|
|
||||||
|
for (Pointer dest (*this); --numSamples >= 0;)
|
||||||
|
{
|
||||||
|
dest.data.copyFromSameType (source.data);
|
||||||
|
dest.advance();
|
||||||
|
source.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Writes a stream of samples into this pointer from another pointer.
|
||||||
|
This will copy the specified number of samples, converting between formats appropriately.
|
||||||
|
*/
|
||||||
|
template <class OtherPointerType>
|
||||||
|
void convertSamples (OtherPointerType source, int numSamples) const noexcept
|
||||||
|
{
|
||||||
|
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
|
||||||
|
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer");
|
||||||
|
|
||||||
|
Pointer dest (*this);
|
||||||
|
|
||||||
|
if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples())
|
||||||
|
{
|
||||||
|
while (--numSamples >= 0)
|
||||||
|
{
|
||||||
|
Endianness::copyFrom (dest.data, source);
|
||||||
|
dest.advance();
|
||||||
|
++source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // copy backwards if we're increasing the sample width..
|
||||||
|
{
|
||||||
|
dest += numSamples;
|
||||||
|
source += numSamples;
|
||||||
|
|
||||||
|
while (--numSamples >= 0)
|
||||||
|
Endianness::copyFrom ((--dest).data, --source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a number of samples to zero. */
|
||||||
|
void clearSamples (int numSamples) const noexcept
|
||||||
|
{
|
||||||
|
Pointer dest (*this);
|
||||||
|
dest.clear (dest.data, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scans a block of data, returning the lowest and highest levels as floats */
|
||||||
|
Range<float> findMinAndMax (size_t numSamples) const noexcept
|
||||||
|
{
|
||||||
|
if (numSamples == 0)
|
||||||
|
return Range<float>();
|
||||||
|
|
||||||
|
Pointer dest (*this);
|
||||||
|
|
||||||
|
if (isFloatingPoint())
|
||||||
|
{
|
||||||
|
float mn = dest.getAsFloat();
|
||||||
|
dest.advance();
|
||||||
|
float mx = mn;
|
||||||
|
|
||||||
|
while (--numSamples > 0)
|
||||||
|
{
|
||||||
|
const float v = dest.getAsFloat();
|
||||||
|
dest.advance();
|
||||||
|
|
||||||
|
if (mx < v) mx = v;
|
||||||
|
if (v < mn) mn = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Range<float> (mn, mx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 mn = dest.getAsInt32();
|
||||||
|
dest.advance();
|
||||||
|
int32 mx = mn;
|
||||||
|
|
||||||
|
while (--numSamples > 0)
|
||||||
|
{
|
||||||
|
const int v = dest.getAsInt32();
|
||||||
|
dest.advance();
|
||||||
|
|
||||||
|
if (mx < v) mx = v;
|
||||||
|
if (v < mn) mn = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Range<float> (mn * (float) (1.0 / (1.0 + Int32::maxValue)),
|
||||||
|
mx * (float) (1.0 / (1.0 + Int32::maxValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scans a block of data, returning the lowest and highest levels as floats */
|
||||||
|
void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept
|
||||||
|
{
|
||||||
|
Range<float> r (findMinAndMax (numSamples));
|
||||||
|
minValue = r.getStart();
|
||||||
|
maxValue = r.getEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if the pointer is using a floating-point format. */
|
||||||
|
static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; }
|
||||||
|
|
||||||
|
/** Returns true if the format is big-endian. */
|
||||||
|
static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; }
|
||||||
|
|
||||||
|
/** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */
|
||||||
|
static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; }
|
||||||
|
|
||||||
|
/** Returns the number of interleaved channels in the format. */
|
||||||
|
int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; }
|
||||||
|
|
||||||
|
/** Returns the number of bytes between the start address of each sample. */
|
||||||
|
int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); }
|
||||||
|
|
||||||
|
/** Returns the accuracy of this format when represented as a 32-bit integer.
|
||||||
|
This is the smallest number above 0 that can be represented in the sample format, converted to
|
||||||
|
a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit,
|
||||||
|
its resolution is 0x100.
|
||||||
|
*/
|
||||||
|
static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; }
|
||||||
|
|
||||||
|
/** Returns a pointer to the underlying data. */
|
||||||
|
const void* getRawData() const noexcept { return data.data; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
SampleFormat data;
|
||||||
|
|
||||||
|
inline void advance() noexcept { this->advanceData (data); }
|
||||||
|
|
||||||
|
Pointer operator++ (int); // private to force you to use the more efficient pre-increment!
|
||||||
|
Pointer operator-- (int);
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** A base class for objects that are used to convert between two different sample formats.
|
||||||
|
|
||||||
|
The AudioData::ConverterInstance implements this base class and can be templated, so
|
||||||
|
you can create an instance that converts between two particular formats, and then
|
||||||
|
store this in the abstract base class.
|
||||||
|
|
||||||
|
@see AudioData::ConverterInstance
|
||||||
|
*/
|
||||||
|
class Converter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Converter() {}
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/** Converts a sequence of samples from the converter's source format into the dest format.
|
||||||
|
This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a
|
||||||
|
particular sub-channel of the data to be used.
|
||||||
|
*/
|
||||||
|
virtual void convertSamples (void* destSamples, int destSubChannel,
|
||||||
|
const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A class that converts between two templated AudioData::Pointer types, and which
|
||||||
|
implements the AudioData::Converter interface.
|
||||||
|
|
||||||
|
This can be used as a concrete instance of the AudioData::Converter abstract class.
|
||||||
|
|
||||||
|
@see AudioData::Converter
|
||||||
|
*/
|
||||||
|
template <class SourceSampleType, class DestSampleType>
|
||||||
|
class ConverterInstance : public Converter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1)
|
||||||
|
: sourceChannels (numSourceChannels), destChannels (numDestChannels)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void convertSamples (void* dest, const void* source, int numSamples) const override
|
||||||
|
{
|
||||||
|
SourceSampleType s (source, sourceChannels);
|
||||||
|
DestSampleType d (dest, destChannels);
|
||||||
|
d.convertSamples (s, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertSamples (void* dest, int destSubChannel,
|
||||||
|
const void* source, int sourceSubChannel, int numSamples) const override
|
||||||
|
{
|
||||||
|
jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels);
|
||||||
|
|
||||||
|
SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels);
|
||||||
|
DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels);
|
||||||
|
d.convertSamples (s, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (ConverterInstance)
|
||||||
|
|
||||||
|
const int sourceChannels, destChannels;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A set of routines to convert buffers of 32-bit floating point data to and from
|
||||||
|
various integer formats.
|
||||||
|
|
||||||
|
Note that these functions are deprecated - the AudioData class provides a much more
|
||||||
|
flexible set of conversion classes now.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioDataConverters
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2);
|
||||||
|
static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2);
|
||||||
|
|
||||||
|
static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3);
|
||||||
|
static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3);
|
||||||
|
|
||||||
|
static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
|
||||||
|
static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
|
||||||
|
|
||||||
|
static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
|
||||||
|
static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2);
|
||||||
|
static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2);
|
||||||
|
|
||||||
|
static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3);
|
||||||
|
static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3);
|
||||||
|
|
||||||
|
static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
|
||||||
|
static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
|
||||||
|
|
||||||
|
static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
|
||||||
|
static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
enum DataFormat
|
||||||
|
{
|
||||||
|
int16LE,
|
||||||
|
int16BE,
|
||||||
|
int24LE,
|
||||||
|
int24BE,
|
||||||
|
int32LE,
|
||||||
|
int32BE,
|
||||||
|
float32LE,
|
||||||
|
float32BE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void convertFloatToFormat (DataFormat destFormat,
|
||||||
|
const float* source, void* dest, int numSamples);
|
||||||
|
|
||||||
|
static void convertFormatToFloat (DataFormat sourceFormat,
|
||||||
|
const void* source, float* dest, int numSamples);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
static void interleaveSamples (const float** source, float* dest,
|
||||||
|
int numSamples, int numChannels);
|
||||||
|
|
||||||
|
static void deinterleaveSamples (const float* source, float** dest,
|
||||||
|
int numSamples, int numChannels);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioDataConverters();
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (AudioDataConverters)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
1136
modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
Normal file
1136
modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
Normal file
File diff suppressed because it is too large
Load Diff
1302
modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
Normal file
1302
modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
Normal file
File diff suppressed because it is too large
Load Diff
257
modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h
Normal file
257
modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifndef JUCE_SNAP_TO_ZERO
|
||||||
|
#if JUCE_INTEL
|
||||||
|
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0;
|
||||||
|
#else
|
||||||
|
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
class ScopedNoDenormals;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A collection of simple vector operations on arrays of floats, accelerated with
|
||||||
|
SIMD instructions where possible.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API FloatVectorOperations
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Clears a vector of floats. */
|
||||||
|
static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Clears a vector of doubles. */
|
||||||
|
static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a repeated value into a vector of floats. */
|
||||||
|
static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a repeated value into a vector of doubles. */
|
||||||
|
static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a vector of floats. */
|
||||||
|
static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a vector of doubles. */
|
||||||
|
static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a vector of floats, multiplying each value by a given multiplier */
|
||||||
|
static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a vector of doubles, multiplying each value by a given multiplier */
|
||||||
|
static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds a fixed value to the destination values. */
|
||||||
|
static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds a fixed value to the destination values. */
|
||||||
|
static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds a fixed value to each source value and stores it in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds a fixed value to each source value and stores it in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds the source values to the destination values. */
|
||||||
|
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds the source values to the destination values. */
|
||||||
|
static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Subtracts the source values from the destination values. */
|
||||||
|
static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Subtracts the source values from the destination values. */
|
||||||
|
static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */
|
||||||
|
static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies the destination values by the source values. */
|
||||||
|
static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies the destination values by the source values. */
|
||||||
|
static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each of the destination values by a fixed multiplier. */
|
||||||
|
static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each of the destination values by a fixed multiplier. */
|
||||||
|
static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */
|
||||||
|
static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept;
|
||||||
|
|
||||||
|
/** Copies a source vector to a destination, negating each value. */
|
||||||
|
static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a source vector to a destination, negating each value. */
|
||||||
|
static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a source vector to a destination, taking the absolute of each value. */
|
||||||
|
static void JUCE_CALLTYPE abs (float* dest, const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Copies a source vector to a destination, taking the absolute of each value. */
|
||||||
|
static void JUCE_CALLTYPE abs (double* dest, const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Converts a stream of integers to floats, multiplying each one by the given multiplier. */
|
||||||
|
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */
|
||||||
|
static void JUCE_CALLTYPE min (float* dest, const float* src, float comp, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */
|
||||||
|
static void JUCE_CALLTYPE min (double* dest, const double* src, double comp, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the minimum of the corresponding source1 and source2 values. */
|
||||||
|
static void JUCE_CALLTYPE min (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the minimum of the corresponding source1 and source2 values. */
|
||||||
|
static void JUCE_CALLTYPE min (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */
|
||||||
|
static void JUCE_CALLTYPE max (float* dest, const float* src, float comp, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */
|
||||||
|
static void JUCE_CALLTYPE max (double* dest, const double* src, double comp, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the maximum of the corresponding source1 and source2 values. */
|
||||||
|
static void JUCE_CALLTYPE max (float* dest, const float* src1, const float* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest will be the maximum of the corresponding source1 and source2 values. */
|
||||||
|
static void JUCE_CALLTYPE max (double* dest, const double* src1, const double* src2, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */
|
||||||
|
static void JUCE_CALLTYPE clip (float* dest, const float* src, float low, float high, int num) noexcept;
|
||||||
|
|
||||||
|
/** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */
|
||||||
|
static void JUCE_CALLTYPE clip (double* dest, const double* src, double low, double high, int num) noexcept;
|
||||||
|
|
||||||
|
/** Finds the minimum and maximum values in the given array. */
|
||||||
|
static Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Finds the minimum and maximum values in the given array. */
|
||||||
|
static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Finds the minimum value in the given array. */
|
||||||
|
static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Finds the minimum value in the given array. */
|
||||||
|
static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Finds the maximum value in the given array. */
|
||||||
|
static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** Finds the maximum value in the given array. */
|
||||||
|
static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept;
|
||||||
|
|
||||||
|
/** This method enables or disables the SSE/NEON flush-to-zero mode. */
|
||||||
|
static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept;
|
||||||
|
|
||||||
|
/** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes.
|
||||||
|
This effectively sets the DAZ and FZ bits of the MXCSR register. On arm CPUs this will
|
||||||
|
enable flush to zero mode.
|
||||||
|
It's a convenient thing to call before audio processing code where you really want to
|
||||||
|
avoid denormalisation performance hits.
|
||||||
|
*/
|
||||||
|
static void JUCE_CALLTYPE disableDenormalisedNumberSupport (bool shouldDisable = true) noexcept;
|
||||||
|
|
||||||
|
/** This method returns true if denormals are currently disabled. */
|
||||||
|
static bool JUCE_CALLTYPE areDenormalsDisabled() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend ScopedNoDenormals;
|
||||||
|
|
||||||
|
static intptr_t JUCE_CALLTYPE getFpStatusRegister() noexcept;
|
||||||
|
static void JUCE_CALLTYPE setFpStatusRegister (intptr_t) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Helper class providing an RAII-based mechanism for temporarily disabling
|
||||||
|
denormals on your CPU.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class ScopedNoDenormals
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedNoDenormals() noexcept;
|
||||||
|
~ScopedNoDenormals() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__))
|
||||||
|
intptr_t fpsr;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
struct CatmullRomAlgorithm
|
||||||
|
{
|
||||||
|
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept
|
||||||
|
{
|
||||||
|
auto y0 = inputs[3];
|
||||||
|
auto y1 = inputs[2];
|
||||||
|
auto y2 = inputs[1];
|
||||||
|
auto y3 = inputs[0];
|
||||||
|
|
||||||
|
auto halfY0 = 0.5f * y0;
|
||||||
|
auto halfY3 = 0.5f * y3;
|
||||||
|
|
||||||
|
return y1 + offset * ((0.5f * y2 - halfY0)
|
||||||
|
+ (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1))
|
||||||
|
+ (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2))))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); }
|
||||||
|
CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {}
|
||||||
|
|
||||||
|
void CatmullRomInterpolator::reset() noexcept
|
||||||
|
{
|
||||||
|
subSamplePos = 1.0;
|
||||||
|
|
||||||
|
for (auto& s : lastInputSamples)
|
||||||
|
s = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||||
|
{
|
||||||
|
return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept
|
||||||
|
{
|
||||||
|
return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept
|
||||||
|
{
|
||||||
|
return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept
|
||||||
|
{
|
||||||
|
return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
143
modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h
Normal file
143
modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
Interpolator for resampling a stream of floats using Catmull-Rom interpolation.
|
||||||
|
|
||||||
|
Note that the resampler is stateful, so when there's a break in the continuity
|
||||||
|
of the input stream you're feeding it, you should call reset() before feeding
|
||||||
|
it any new data. And like with any other stateful filter, if you're resampling
|
||||||
|
multiple channels, make sure each one uses its own CatmullRomInterpolator
|
||||||
|
object.
|
||||||
|
|
||||||
|
@see LagrangeInterpolator
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API CatmullRomInterpolator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CatmullRomInterpolator() noexcept;
|
||||||
|
~CatmullRomInterpolator() noexcept;
|
||||||
|
|
||||||
|
/** Resets the state of the interpolator.
|
||||||
|
Call this when there's a break in the continuity of the input data stream.
|
||||||
|
*/
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results into
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int process (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results into
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param available the number of available input samples. If it needs more samples
|
||||||
|
than available, it either wraps back for wrapAround samples, or
|
||||||
|
it feeds zeroes
|
||||||
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||||
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int process (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
int available,
|
||||||
|
int wrapAround) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples, adding the results to the output data
|
||||||
|
with a gain.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results to - the result values will be added
|
||||||
|
to any pre-existing data in this buffer after being multiplied by
|
||||||
|
the gain factor
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param gain a gain factor to multiply the resulting samples by before
|
||||||
|
adding them to the destination buffer
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int processAdding (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
float gain) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples, adding the results to the output data
|
||||||
|
with a gain.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results to - the result values will be added
|
||||||
|
to any pre-existing data in this buffer after being multiplied by
|
||||||
|
the gain factor
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param available the number of available input samples. If it needs more samples
|
||||||
|
than available, it either wraps back for wrapAround samples, or
|
||||||
|
it feeds zeroes
|
||||||
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||||
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||||
|
@param gain a gain factor to multiply the resulting samples by before
|
||||||
|
adding them to the destination buffer
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int processAdding (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
int available,
|
||||||
|
int wrapAround,
|
||||||
|
float gain) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float lastInputSamples[5];
|
||||||
|
double subSamplePos;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
112
modules/juce_audio_basics/effects/juce_Decibels.h
Normal file
112
modules/juce_audio_basics/effects/juce_Decibels.h
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class contains some helpful static methods for dealing with decibel values.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class Decibels
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Converts a dBFS value to its equivalent gain level.
|
||||||
|
|
||||||
|
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any
|
||||||
|
decibel value lower than minusInfinityDb will return a gain of 0.
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
static Type decibelsToGain (Type decibels,
|
||||||
|
Type minusInfinityDb = Type (defaultMinusInfinitydB))
|
||||||
|
{
|
||||||
|
return decibels > minusInfinityDb ? std::pow (Type (10.0), decibels * Type (0.05))
|
||||||
|
: Type();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a gain level into a dBFS value.
|
||||||
|
|
||||||
|
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values.
|
||||||
|
If the gain is 0 (or negative), then the method will return the value
|
||||||
|
provided as minusInfinityDb.
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
static Type gainToDecibels (Type gain,
|
||||||
|
Type minusInfinityDb = Type (defaultMinusInfinitydB))
|
||||||
|
{
|
||||||
|
return gain > Type() ? jmax (minusInfinityDb, static_cast<Type> (std::log10 (gain)) * Type (20.0))
|
||||||
|
: minusInfinityDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Converts a decibel reading to a string.
|
||||||
|
|
||||||
|
By default the returned string will have the 'dB' suffix added, but this can be removed by
|
||||||
|
setting the shouldIncludeSuffix argument to false. If a customMinusInfinityString argument
|
||||||
|
is provided this will be returned if the value is lower than minusInfinityDb, otherwise
|
||||||
|
the return value will be "-INF".
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
static String toString (Type decibels,
|
||||||
|
int decimalPlaces = 2,
|
||||||
|
Type minusInfinityDb = Type (defaultMinusInfinitydB),
|
||||||
|
bool shouldIncludeSuffix = true,
|
||||||
|
StringRef customMinusInfinityString = {})
|
||||||
|
{
|
||||||
|
String s;
|
||||||
|
s.preallocateBytes (20);
|
||||||
|
|
||||||
|
if (decibels <= minusInfinityDb)
|
||||||
|
{
|
||||||
|
if (customMinusInfinityString.isEmpty())
|
||||||
|
s << "-INF";
|
||||||
|
else
|
||||||
|
s << customMinusInfinityString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (decibels >= Type())
|
||||||
|
s << '+';
|
||||||
|
|
||||||
|
if (decimalPlaces <= 0)
|
||||||
|
s << roundToInt (decibels);
|
||||||
|
else
|
||||||
|
s << String (decibels, decimalPlaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldIncludeSuffix)
|
||||||
|
s << " dB";
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
enum { defaultMinusInfinitydB = -100 };
|
||||||
|
|
||||||
|
Decibels() = delete; // This class can't be instantiated, it's just a holder for static methods..
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
336
modules/juce_audio_basics/effects/juce_IIRFilter.cpp
Normal file
336
modules/juce_audio_basics/effects/juce_IIRFilter.cpp
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
IIRCoefficients::IIRCoefficients() noexcept
|
||||||
|
{
|
||||||
|
zeromem (coefficients, sizeof (coefficients));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients::~IIRCoefficients() noexcept {}
|
||||||
|
|
||||||
|
IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept
|
||||||
|
{
|
||||||
|
memcpy (coefficients, other.coefficients, sizeof (coefficients));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept
|
||||||
|
{
|
||||||
|
memcpy (coefficients, other.coefficients, sizeof (coefficients));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients::IIRCoefficients (double c1, double c2, double c3,
|
||||||
|
double c4, double c5, double c6) noexcept
|
||||||
|
{
|
||||||
|
auto a = 1.0 / c4;
|
||||||
|
|
||||||
|
coefficients[0] = (float) (c1 * a);
|
||||||
|
coefficients[1] = (float) (c2 * a);
|
||||||
|
coefficients[2] = (float) (c3 * a);
|
||||||
|
coefficients[3] = (float) (c5 * a);
|
||||||
|
coefficients[4] = (float) (c6 * a);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate,
|
||||||
|
double frequency) noexcept
|
||||||
|
{
|
||||||
|
return makeLowPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||||
|
auto nSquared = n * n;
|
||||||
|
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||||
|
|
||||||
|
return IIRCoefficients (c1,
|
||||||
|
c1 * 2.0,
|
||||||
|
c1,
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate,
|
||||||
|
double frequency) noexcept
|
||||||
|
{
|
||||||
|
return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto n = std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||||
|
auto nSquared = n * n;
|
||||||
|
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||||
|
|
||||||
|
return IIRCoefficients (c1,
|
||||||
|
c1 * -2.0,
|
||||||
|
c1,
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (nSquared - 1.0),
|
||||||
|
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate,
|
||||||
|
double frequency) noexcept
|
||||||
|
{
|
||||||
|
return makeBandPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||||
|
auto nSquared = n * n;
|
||||||
|
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||||
|
|
||||||
|
return IIRCoefficients (c1 * n / Q,
|
||||||
|
0.0,
|
||||||
|
-c1 * n / Q,
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate,
|
||||||
|
double frequency) noexcept
|
||||||
|
{
|
||||||
|
return makeNotchFilter (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||||
|
auto nSquared = n * n;
|
||||||
|
auto c1 = 1.0 / (1.0 + n / Q + nSquared);
|
||||||
|
|
||||||
|
return IIRCoefficients (c1 * (1.0 + nSquared),
|
||||||
|
2.0 * c1 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 + nSquared),
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 - n / Q + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate,
|
||||||
|
double frequency) noexcept
|
||||||
|
{
|
||||||
|
return makeAllPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||||
|
auto nSquared = n * n;
|
||||||
|
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||||
|
|
||||||
|
return IIRCoefficients (c1 * (1.0 - n / Q + nSquared),
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 - n / Q + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeLowShelf (double sampleRate,
|
||||||
|
double cutOffFrequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||||
|
auto aminus1 = A - 1.0;
|
||||||
|
auto aplus1 = A + 1.0;
|
||||||
|
auto omega = (MathConstants<double>::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate;
|
||||||
|
auto coso = std::cos (omega);
|
||||||
|
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||||
|
auto aminus1TimesCoso = aminus1 * coso;
|
||||||
|
|
||||||
|
return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta),
|
||||||
|
A * 2.0 * (aminus1 - aplus1 * coso),
|
||||||
|
A * (aplus1 - aminus1TimesCoso - beta),
|
||||||
|
aplus1 + aminus1TimesCoso + beta,
|
||||||
|
-2.0 * (aminus1 + aplus1 * coso),
|
||||||
|
aplus1 + aminus1TimesCoso - beta);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makeHighShelf (double sampleRate,
|
||||||
|
double cutOffFrequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||||
|
auto aminus1 = A - 1.0;
|
||||||
|
auto aplus1 = A + 1.0;
|
||||||
|
auto omega = (MathConstants<double>::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate;
|
||||||
|
auto coso = std::cos (omega);
|
||||||
|
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||||
|
auto aminus1TimesCoso = aminus1 * coso;
|
||||||
|
|
||||||
|
return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta),
|
||||||
|
A * -2.0 * (aminus1 + aplus1 * coso),
|
||||||
|
A * (aplus1 + aminus1TimesCoso - beta),
|
||||||
|
aplus1 - aminus1TimesCoso + beta,
|
||||||
|
2.0 * (aminus1 - aplus1 * coso),
|
||||||
|
aplus1 - aminus1TimesCoso - beta);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0.0);
|
||||||
|
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||||
|
jassert (Q > 0.0);
|
||||||
|
|
||||||
|
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||||
|
auto omega = (MathConstants<double>::twoPi * jmax (frequency, 2.0)) / sampleRate;
|
||||||
|
auto alpha = 0.5 * std::sin (omega) / Q;
|
||||||
|
auto c2 = -2.0 * std::cos (omega);
|
||||||
|
auto alphaTimesA = alpha * A;
|
||||||
|
auto alphaOverA = alpha / A;
|
||||||
|
|
||||||
|
return IIRCoefficients (1.0 + alphaTimesA,
|
||||||
|
c2,
|
||||||
|
1.0 - alphaTimesA,
|
||||||
|
1.0 + alphaOverA,
|
||||||
|
c2,
|
||||||
|
1.0 - alphaOverA);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
IIRFilter::IIRFilter() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRFilter::IIRFilter (const IIRFilter& other) noexcept : active (other.active)
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (other.processLock);
|
||||||
|
coefficients = other.coefficients;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRFilter::~IIRFilter() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void IIRFilter::makeInactive() noexcept
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (processLock);
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (processLock);
|
||||||
|
coefficients = newCoefficients;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void IIRFilter::reset() noexcept
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (processLock);
|
||||||
|
v1 = v2 = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float IIRFilter::processSingleSampleRaw (float in) noexcept
|
||||||
|
{
|
||||||
|
auto out = coefficients.coefficients[0] * in + v1;
|
||||||
|
|
||||||
|
JUCE_SNAP_TO_ZERO (out);
|
||||||
|
|
||||||
|
v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2;
|
||||||
|
v2 = coefficients.coefficients[2] * in - coefficients.coefficients[4] * out;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (processLock);
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
auto c0 = coefficients.coefficients[0];
|
||||||
|
auto c1 = coefficients.coefficients[1];
|
||||||
|
auto c2 = coefficients.coefficients[2];
|
||||||
|
auto c3 = coefficients.coefficients[3];
|
||||||
|
auto c4 = coefficients.coefficients[4];
|
||||||
|
auto lv1 = v1, lv2 = v2;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
auto in = samples[i];
|
||||||
|
auto out = c0 * in + lv1;
|
||||||
|
samples[i] = out;
|
||||||
|
|
||||||
|
lv1 = c1 * in - c3 * out + lv2;
|
||||||
|
lv2 = c2 * in - c4 * out;
|
||||||
|
}
|
||||||
|
|
||||||
|
JUCE_SNAP_TO_ZERO (lv1); v1 = lv1;
|
||||||
|
JUCE_SNAP_TO_ZERO (lv2); v2 = lv2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
217
modules/juce_audio_basics/effects/juce_IIRFilter.h
Normal file
217
modules/juce_audio_basics/effects/juce_IIRFilter.h
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
class IIRFilter;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A set of coefficients for use in an IIRFilter object.
|
||||||
|
|
||||||
|
@see IIRFilter
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API IIRCoefficients
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a null set of coefficients (which will produce silence). */
|
||||||
|
IIRCoefficients() noexcept;
|
||||||
|
|
||||||
|
/** Directly constructs an object from the raw coefficients.
|
||||||
|
Most people will want to use the static methods instead of this, but
|
||||||
|
the constructor is public to allow tinkerers to create their own custom
|
||||||
|
filters!
|
||||||
|
*/
|
||||||
|
IIRCoefficients (double c1, double c2, double c3,
|
||||||
|
double c4, double c5, double c6) noexcept;
|
||||||
|
|
||||||
|
/** Creates a copy of another filter. */
|
||||||
|
IIRCoefficients (const IIRCoefficients&) noexcept;
|
||||||
|
/** Creates a copy of another filter. */
|
||||||
|
IIRCoefficients& operator= (const IIRCoefficients&) noexcept;
|
||||||
|
/** Destructor. */
|
||||||
|
~IIRCoefficients() noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for a low-pass filter. */
|
||||||
|
static IIRCoefficients makeLowPass (double sampleRate,
|
||||||
|
double frequency) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a low-pass filter with variable Q. */
|
||||||
|
static IIRCoefficients makeLowPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for a high-pass filter. */
|
||||||
|
static IIRCoefficients makeHighPass (double sampleRate,
|
||||||
|
double frequency) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a high-pass filter with variable Q. */
|
||||||
|
static IIRCoefficients makeHighPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for a band-pass filter. */
|
||||||
|
static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a band-pass filter with variable Q. */
|
||||||
|
static IIRCoefficients makeBandPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for a notch filter. */
|
||||||
|
static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a notch filter with variable Q. */
|
||||||
|
static IIRCoefficients makeNotchFilter (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for an all-pass filter. */
|
||||||
|
static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for an all-pass filter with variable Q. */
|
||||||
|
static IIRCoefficients makeAllPass (double sampleRate,
|
||||||
|
double frequency,
|
||||||
|
double Q) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the coefficients for a low-pass shelf filter with variable Q and gain.
|
||||||
|
|
||||||
|
The gain is a scale factor that the low frequencies are multiplied by, so values
|
||||||
|
greater than 1.0 will boost the low frequencies, values less than 1.0 will
|
||||||
|
attenuate them.
|
||||||
|
*/
|
||||||
|
static IIRCoefficients makeLowShelf (double sampleRate,
|
||||||
|
double cutOffFrequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a high-pass shelf filter with variable Q and gain.
|
||||||
|
|
||||||
|
The gain is a scale factor that the high frequencies are multiplied by, so values
|
||||||
|
greater than 1.0 will boost the high frequencies, values less than 1.0 will
|
||||||
|
attenuate them.
|
||||||
|
*/
|
||||||
|
static IIRCoefficients makeHighShelf (double sampleRate,
|
||||||
|
double cutOffFrequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients for a peak filter centred around a
|
||||||
|
given frequency, with a variable Q and gain.
|
||||||
|
|
||||||
|
The gain is a scale factor that the centre frequencies are multiplied by, so
|
||||||
|
values greater than 1.0 will boost the centre frequencies, values less than
|
||||||
|
1.0 will attenuate them.
|
||||||
|
*/
|
||||||
|
static IIRCoefficients makePeakFilter (double sampleRate,
|
||||||
|
double centreFrequency,
|
||||||
|
double Q,
|
||||||
|
float gainFactor) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** The raw coefficients.
|
||||||
|
You should leave these numbers alone unless you really know what you're doing.
|
||||||
|
*/
|
||||||
|
float coefficients[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An IIR filter that can perform low, high, or band-pass filtering on an
|
||||||
|
audio signal.
|
||||||
|
|
||||||
|
@see IIRCoefficient, IIRFilterAudioSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API IIRFilter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a filter.
|
||||||
|
|
||||||
|
Initially the filter is inactive, so will have no effect on samples that
|
||||||
|
you process with it. Use the setCoefficients() method to turn it into the
|
||||||
|
type of filter needed.
|
||||||
|
*/
|
||||||
|
IIRFilter() noexcept;
|
||||||
|
|
||||||
|
/** Creates a copy of another filter. */
|
||||||
|
IIRFilter (const IIRFilter&) noexcept;
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~IIRFilter() noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Clears the filter so that any incoming data passes through unchanged. */
|
||||||
|
void makeInactive() noexcept;
|
||||||
|
|
||||||
|
/** Applies a set of coefficients to this filter. */
|
||||||
|
void setCoefficients (const IIRCoefficients& newCoefficients) noexcept;
|
||||||
|
|
||||||
|
/** Returns the coefficients that this filter is using. */
|
||||||
|
IIRCoefficients getCoefficients() const noexcept { return coefficients; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Resets the filter's processing pipeline, ready to start a new stream of data.
|
||||||
|
|
||||||
|
Note that this clears the processing state, but the type of filter and
|
||||||
|
its coefficients aren't changed. To put a filter into an inactive state, use
|
||||||
|
the makeInactive() method.
|
||||||
|
*/
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
/** Performs the filter operation on the given set of samples. */
|
||||||
|
void processSamples (float* samples, int numSamples) noexcept;
|
||||||
|
|
||||||
|
/** Processes a single sample, without any locking or checking.
|
||||||
|
|
||||||
|
Use this if you need fast processing of a single value, but be aware that
|
||||||
|
this isn't thread-safe in the way that processSamples() is.
|
||||||
|
*/
|
||||||
|
float processSingleSampleRaw (float sample) noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
SpinLock processLock;
|
||||||
|
IIRCoefficients coefficients;
|
||||||
|
float v1 = 0, v2 = 0;
|
||||||
|
bool active = false;
|
||||||
|
|
||||||
|
// The exact meaning of an assignment operator would be ambiguous since the filters are
|
||||||
|
// stateful. If you want to copy the coefficients, then just use setCoefficients().
|
||||||
|
IIRFilter& operator= (const IIRFilter&) = delete;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR (IIRFilter)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
459
modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp
Normal file
459
modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static forcedinline void pushInterpolationSample (float* lastInputSamples, float newValue) noexcept
|
||||||
|
{
|
||||||
|
lastInputSamples[4] = lastInputSamples[3];
|
||||||
|
lastInputSamples[3] = lastInputSamples[2];
|
||||||
|
lastInputSamples[2] = lastInputSamples[1];
|
||||||
|
lastInputSamples[1] = lastInputSamples[0];
|
||||||
|
lastInputSamples[0] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept
|
||||||
|
{
|
||||||
|
if (numOut >= 5)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
lastInputSamples[i] = input[--numOut];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input,
|
||||||
|
int numOut, int available, int wrapAround) noexcept
|
||||||
|
{
|
||||||
|
if (numOut >= 5)
|
||||||
|
{
|
||||||
|
if (available >= 5)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
lastInputSamples[i] = input[--numOut];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < available; ++i)
|
||||||
|
lastInputSamples[i] = input[--numOut];
|
||||||
|
|
||||||
|
if (wrapAround > 0)
|
||||||
|
{
|
||||||
|
numOut -= wrapAround;
|
||||||
|
|
||||||
|
for (int i = available; i < 5; ++i)
|
||||||
|
lastInputSamples[i] = input[--numOut];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = available; i < 5; ++i)
|
||||||
|
lastInputSamples[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (numOut > available)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < available; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, input[i]);
|
||||||
|
|
||||||
|
if (wrapAround > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut - available; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, input[i + available - wrapAround]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut - available; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InterpolatorType>
|
||||||
|
static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||||
|
const float* in, float* out, int numOut) noexcept
|
||||||
|
{
|
||||||
|
auto pos = subSamplePos;
|
||||||
|
|
||||||
|
if (actualRatio == 1.0 && pos == 1.0)
|
||||||
|
{
|
||||||
|
memcpy (out, in, (size_t) numOut * sizeof (float));
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut);
|
||||||
|
return numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numUsed = 0;
|
||||||
|
|
||||||
|
while (numOut > 0)
|
||||||
|
{
|
||||||
|
while (pos >= 1.0)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, in[numUsed++]);
|
||||||
|
pos -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||||
|
pos += actualRatio;
|
||||||
|
--numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
subSamplePos = pos;
|
||||||
|
return numUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InterpolatorType>
|
||||||
|
static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||||
|
const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||||
|
{
|
||||||
|
if (actualRatio == 1.0)
|
||||||
|
{
|
||||||
|
if (available >= numOut)
|
||||||
|
{
|
||||||
|
memcpy (out, in, (size_t) numOut * sizeof (float));
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memcpy (out, in, (size_t) available * sizeof (float));
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||||
|
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
memcpy (out + available, in + available - wrap, (size_t) (numOut - available) * sizeof (float));
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut - available; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto originalIn = in;
|
||||||
|
auto pos = subSamplePos;
|
||||||
|
bool exceeded = false;
|
||||||
|
|
||||||
|
if (actualRatio < 1.0)
|
||||||
|
{
|
||||||
|
for (int i = numOut; --i >= 0;)
|
||||||
|
{
|
||||||
|
if (pos >= 1.0)
|
||||||
|
{
|
||||||
|
if (exceeded)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, *in++);
|
||||||
|
|
||||||
|
if (--available <= 0)
|
||||||
|
{
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
in -= wrap;
|
||||||
|
available += wrap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exceeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||||
|
pos += actualRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = numOut; --i >= 0;)
|
||||||
|
{
|
||||||
|
while (pos < actualRatio)
|
||||||
|
{
|
||||||
|
if (exceeded)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, *in++);
|
||||||
|
|
||||||
|
if (--available <= 0)
|
||||||
|
{
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
in -= wrap;
|
||||||
|
available += wrap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exceeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos -= actualRatio;
|
||||||
|
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subSamplePos = pos;
|
||||||
|
return ((int) (in - originalIn) + wrap) % wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InterpolatorType>
|
||||||
|
static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||||
|
const float* in, float* out, int numOut,
|
||||||
|
int available, int wrap, float gain) noexcept
|
||||||
|
{
|
||||||
|
if (actualRatio == 1.0)
|
||||||
|
{
|
||||||
|
if (available >= numOut)
|
||||||
|
{
|
||||||
|
FloatVectorOperations::addWithMultiply (out, in, gain, numOut);
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FloatVectorOperations::addWithMultiply (out, in, gain, available);
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, available, available, wrap);
|
||||||
|
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
FloatVectorOperations::addWithMultiply (out, in - wrap, gain, numOut - available);
|
||||||
|
pushInterpolationSamples (lastInputSamples, in - wrap, numOut - available, available, wrap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numOut-available; ++i)
|
||||||
|
pushInterpolationSample (lastInputSamples, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto originalIn = in;
|
||||||
|
auto pos = subSamplePos;
|
||||||
|
bool exceeded = false;
|
||||||
|
|
||||||
|
if (actualRatio < 1.0)
|
||||||
|
{
|
||||||
|
for (int i = numOut; --i >= 0;)
|
||||||
|
{
|
||||||
|
if (pos >= 1.0)
|
||||||
|
{
|
||||||
|
if (exceeded)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, 0.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, *in++);
|
||||||
|
|
||||||
|
if (--available <= 0)
|
||||||
|
{
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
in -= wrap;
|
||||||
|
available += wrap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exceeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||||
|
pos += actualRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = numOut; --i >= 0;)
|
||||||
|
{
|
||||||
|
while (pos < actualRatio)
|
||||||
|
{
|
||||||
|
if (exceeded)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, 0.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, *in++);
|
||||||
|
|
||||||
|
if (--available <= 0)
|
||||||
|
{
|
||||||
|
if (wrap > 0)
|
||||||
|
{
|
||||||
|
in -= wrap;
|
||||||
|
available += wrap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exceeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos -= actualRatio;
|
||||||
|
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subSamplePos = pos;
|
||||||
|
return ((int) (in - originalIn) + wrap) % wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InterpolatorType>
|
||||||
|
static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||||
|
const float* in, float* out, int numOut, float gain) noexcept
|
||||||
|
{
|
||||||
|
auto pos = subSamplePos;
|
||||||
|
|
||||||
|
if (actualRatio == 1.0 && pos == 1.0)
|
||||||
|
{
|
||||||
|
FloatVectorOperations::addWithMultiply (out, in, gain, numOut);
|
||||||
|
pushInterpolationSamples (lastInputSamples, in, numOut);
|
||||||
|
return numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numUsed = 0;
|
||||||
|
|
||||||
|
while (numOut > 0)
|
||||||
|
{
|
||||||
|
while (pos >= 1.0)
|
||||||
|
{
|
||||||
|
pushInterpolationSample (lastInputSamples, in[numUsed++]);
|
||||||
|
pos -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||||
|
pos += actualRatio;
|
||||||
|
--numOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
subSamplePos = pos;
|
||||||
|
return numUsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
template <int k>
|
||||||
|
struct LagrangeResampleHelper
|
||||||
|
{
|
||||||
|
static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct LagrangeResampleHelper<0>
|
||||||
|
{
|
||||||
|
static forcedinline void calc (float&, float) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LagrangeAlgorithm
|
||||||
|
{
|
||||||
|
static forcedinline float valueAtOffset (const float* inputs, float offset) noexcept
|
||||||
|
{
|
||||||
|
return calcCoefficient<0> (inputs[4], offset)
|
||||||
|
+ calcCoefficient<1> (inputs[3], offset)
|
||||||
|
+ calcCoefficient<2> (inputs[2], offset)
|
||||||
|
+ calcCoefficient<3> (inputs[1], offset)
|
||||||
|
+ calcCoefficient<4> (inputs[0], offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int k>
|
||||||
|
static forcedinline float calcCoefficient (float input, float offset) noexcept
|
||||||
|
{
|
||||||
|
LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset);
|
||||||
|
LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset);
|
||||||
|
LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset);
|
||||||
|
LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset);
|
||||||
|
LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); }
|
||||||
|
LagrangeInterpolator::~LagrangeInterpolator() noexcept {}
|
||||||
|
|
||||||
|
void LagrangeInterpolator::reset() noexcept
|
||||||
|
{
|
||||||
|
subSamplePos = 1.0;
|
||||||
|
|
||||||
|
for (auto& s : lastInputSamples)
|
||||||
|
s = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||||
|
{
|
||||||
|
return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept
|
||||||
|
{
|
||||||
|
return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept
|
||||||
|
{
|
||||||
|
return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept
|
||||||
|
{
|
||||||
|
return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
143
modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h
Normal file
143
modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
Interpolator for resampling a stream of floats using 4-point lagrange interpolation.
|
||||||
|
|
||||||
|
Note that the resampler is stateful, so when there's a break in the continuity
|
||||||
|
of the input stream you're feeding it, you should call reset() before feeding
|
||||||
|
it any new data. And like with any other stateful filter, if you're resampling
|
||||||
|
multiple channels, make sure each one uses its own LagrangeInterpolator
|
||||||
|
object.
|
||||||
|
|
||||||
|
@see CatmullRomInterpolator
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API LagrangeInterpolator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LagrangeInterpolator() noexcept;
|
||||||
|
~LagrangeInterpolator() noexcept;
|
||||||
|
|
||||||
|
/** Resets the state of the interpolator.
|
||||||
|
Call this when there's a break in the continuity of the input data stream.
|
||||||
|
*/
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results into
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int process (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results into
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param available the number of available input samples. If it needs more samples
|
||||||
|
than available, it either wraps back for wrapAround samples, or
|
||||||
|
it feeds zeroes
|
||||||
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||||
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int process (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
int available,
|
||||||
|
int wrapAround) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples, adding the results to the output data
|
||||||
|
with a gain.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results to - the result values will be added
|
||||||
|
to any pre-existing data in this buffer after being multiplied by
|
||||||
|
the gain factor
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param gain a gain factor to multiply the resulting samples by before
|
||||||
|
adding them to the destination buffer
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int processAdding (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
float gain) noexcept;
|
||||||
|
|
||||||
|
/** Resamples a stream of samples, adding the results to the output data
|
||||||
|
with a gain.
|
||||||
|
|
||||||
|
@param speedRatio the number of input samples to use for each output sample
|
||||||
|
@param inputSamples the source data to read from. This must contain at
|
||||||
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||||
|
@param outputSamples the buffer to write the results to - the result values will be added
|
||||||
|
to any pre-existing data in this buffer after being multiplied by
|
||||||
|
the gain factor
|
||||||
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||||
|
@param available the number of available input samples. If it needs more samples
|
||||||
|
than available, it either wraps back for wrapAround samples, or
|
||||||
|
it feeds zeroes
|
||||||
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||||
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||||
|
@param gain a gain factor to multiply the resulting samples by before
|
||||||
|
adding them to the destination buffer
|
||||||
|
|
||||||
|
@returns the actual number of input samples that were used
|
||||||
|
*/
|
||||||
|
int processAdding (double speedRatio,
|
||||||
|
const float* inputSamples,
|
||||||
|
float* outputSamples,
|
||||||
|
int numOutputSamplesToProduce,
|
||||||
|
int available,
|
||||||
|
int wrapAround,
|
||||||
|
float gain) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float lastInputSamples[5];
|
||||||
|
double subSamplePos;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
215
modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h
Normal file
215
modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 <typename FloatType>
|
||||||
|
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<FloatType>& 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<FloatType> (numSamples));
|
||||||
|
countdown -= numSamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
FloatType currentValue = 0, target = 0, step = 0;
|
||||||
|
int countdown = 0, stepsToTarget = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
322
modules/juce_audio_basics/effects/juce_Reverb.h
Normal file
322
modules/juce_audio_basics/effects/juce_Reverb.h
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Performs a simple reverb effect on a stream of audio data.
|
||||||
|
|
||||||
|
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
|
||||||
|
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
|
||||||
|
apply the reverb to your audio data.
|
||||||
|
|
||||||
|
@see ReverbAudioSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class Reverb
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
Reverb()
|
||||||
|
{
|
||||||
|
setParameters (Parameters());
|
||||||
|
setSampleRate (44100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** 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. */
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the reverb's current parameters. */
|
||||||
|
const Parameters& getParameters() const noexcept { return parameters; }
|
||||||
|
|
||||||
|
/** Applies a new set of parameters to the reverb.
|
||||||
|
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
|
||||||
|
the process method, you may get artifacts.
|
||||||
|
*/
|
||||||
|
void setParameters (const Parameters& newParams)
|
||||||
|
{
|
||||||
|
const float wetScaleFactor = 3.0f;
|
||||||
|
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));
|
||||||
|
|
||||||
|
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
|
||||||
|
parameters = newParams;
|
||||||
|
updateDamping();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Sets the sample rate that will be used for the reverb.
|
||||||
|
You must call this before the process methods, in order to tell it the correct sample rate.
|
||||||
|
*/
|
||||||
|
void setSampleRate (const double sampleRate)
|
||||||
|
{
|
||||||
|
jassert (sampleRate > 0);
|
||||||
|
|
||||||
|
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
|
||||||
|
static const short allPassTunings[] = { 556, 441, 341, 225 };
|
||||||
|
const int stereoSpread = 23;
|
||||||
|
const int intSampleRate = (int) sampleRate;
|
||||||
|
|
||||||
|
for (int i = 0; i < numCombs; ++i)
|
||||||
|
{
|
||||||
|
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
|
||||||
|
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < numAllPasses; ++i)
|
||||||
|
{
|
||||||
|
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
|
||||||
|
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const double smoothTime = 0.01;
|
||||||
|
damping .reset (sampleRate, smoothTime);
|
||||||
|
feedback.reset (sampleRate, smoothTime);
|
||||||
|
dryGain .reset (sampleRate, smoothTime);
|
||||||
|
wetGain1.reset (sampleRate, smoothTime);
|
||||||
|
wetGain2.reset (sampleRate, smoothTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the reverb's buffers. */
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
for (int j = 0; j < numChannels; ++j)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numCombs; ++i)
|
||||||
|
comb[j][i].clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < numAllPasses; ++i)
|
||||||
|
allPass[j][i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Applies the reverb to two stereo channels of audio data. */
|
||||||
|
void processStereo (float* const left, float* const right, const int numSamples) noexcept
|
||||||
|
{
|
||||||
|
jassert (left != nullptr && right != nullptr);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float input = (left[i] + right[i]) * gain;
|
||||||
|
float outL = 0, outR = 0;
|
||||||
|
|
||||||
|
const float damp = damping.getNextValue();
|
||||||
|
const float feedbck = feedback.getNextValue();
|
||||||
|
|
||||||
|
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||||
|
{
|
||||||
|
outL += comb[0][j].process (input, damp, feedbck);
|
||||||
|
outR += comb[1][j].process (input, damp, feedbck);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||||
|
{
|
||||||
|
outL = allPass[0][j].process (outL);
|
||||||
|
outR = allPass[1][j].process (outR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float dry = dryGain.getNextValue();
|
||||||
|
const float wet1 = wetGain1.getNextValue();
|
||||||
|
const float wet2 = wetGain2.getNextValue();
|
||||||
|
|
||||||
|
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
|
||||||
|
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies the reverb to a single mono channel of audio data. */
|
||||||
|
void processMono (float* const samples, const int numSamples) noexcept
|
||||||
|
{
|
||||||
|
jassert (samples != nullptr);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float input = samples[i] * gain;
|
||||||
|
float output = 0;
|
||||||
|
|
||||||
|
const float damp = damping.getNextValue();
|
||||||
|
const float feedbck = feedback.getNextValue();
|
||||||
|
|
||||||
|
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||||
|
output += comb[0][j].process (input, damp, feedbck);
|
||||||
|
|
||||||
|
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||||
|
output = allPass[0][j].process (output);
|
||||||
|
|
||||||
|
const float dry = dryGain.getNextValue();
|
||||||
|
const float wet1 = wetGain1.getNextValue();
|
||||||
|
|
||||||
|
samples[i] = output * wet1 + samples[i] * dry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; }
|
||||||
|
|
||||||
|
void updateDamping() noexcept
|
||||||
|
{
|
||||||
|
const float roomScaleFactor = 0.28f;
|
||||||
|
const float roomOffset = 0.7f;
|
||||||
|
const float dampScaleFactor = 0.4f;
|
||||||
|
|
||||||
|
if (isFrozen (parameters.freezeMode))
|
||||||
|
setDamping (0.0f, 1.0f);
|
||||||
|
else
|
||||||
|
setDamping (parameters.damping * dampScaleFactor,
|
||||||
|
parameters.roomSize * roomScaleFactor + roomOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept
|
||||||
|
{
|
||||||
|
damping.setValue (dampingToUse);
|
||||||
|
feedback.setValue (roomSizeToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class CombFilter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CombFilter() noexcept : bufferSize (0), bufferIndex (0), last (0) {}
|
||||||
|
|
||||||
|
void setSize (const int size)
|
||||||
|
{
|
||||||
|
if (size != bufferSize)
|
||||||
|
{
|
||||||
|
bufferIndex = 0;
|
||||||
|
buffer.malloc (size);
|
||||||
|
bufferSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
last = 0;
|
||||||
|
buffer.clear ((size_t) bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
float process (const float input, const float damp, const float feedbackLevel) noexcept
|
||||||
|
{
|
||||||
|
const float output = buffer[bufferIndex];
|
||||||
|
last = (output * (1.0f - damp)) + (last * damp);
|
||||||
|
JUCE_UNDENORMALISE (last);
|
||||||
|
|
||||||
|
float temp = input + (last * feedbackLevel);
|
||||||
|
JUCE_UNDENORMALISE (temp);
|
||||||
|
buffer[bufferIndex] = temp;
|
||||||
|
bufferIndex = (bufferIndex + 1) % bufferSize;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HeapBlock<float> buffer;
|
||||||
|
int bufferSize, bufferIndex;
|
||||||
|
float last;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (CombFilter)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class AllPassFilter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AllPassFilter() noexcept : bufferSize (0), bufferIndex (0) {}
|
||||||
|
|
||||||
|
void setSize (const int size)
|
||||||
|
{
|
||||||
|
if (size != bufferSize)
|
||||||
|
{
|
||||||
|
bufferIndex = 0;
|
||||||
|
buffer.malloc (size);
|
||||||
|
bufferSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
buffer.clear ((size_t) bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
float process (const float input) noexcept
|
||||||
|
{
|
||||||
|
const float bufferedValue = buffer [bufferIndex];
|
||||||
|
float temp = input + (bufferedValue * 0.5f);
|
||||||
|
JUCE_UNDENORMALISE (temp);
|
||||||
|
buffer [bufferIndex] = temp;
|
||||||
|
bufferIndex = (bufferIndex + 1) % bufferSize;
|
||||||
|
return bufferedValue - input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HeapBlock<float> buffer;
|
||||||
|
int bufferSize, bufferIndex;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
|
||||||
|
|
||||||
|
Parameters parameters;
|
||||||
|
float gain;
|
||||||
|
|
||||||
|
CombFilter comb [numChannels][numCombs];
|
||||||
|
AllPassFilter allPass [numChannels][numAllPasses];
|
||||||
|
|
||||||
|
LinearSmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
85
modules/juce_audio_basics/juce_audio_basics.cpp
Normal file
85
modules/juce_audio_basics/juce_audio_basics.cpp
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef JUCE_AUDIO_BASICS_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_audio_basics.h"
|
||||||
|
|
||||||
|
#if JUCE_MINGW && ! defined (alloca)
|
||||||
|
#define alloca __builtin_alloca
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_SSE_INTRINSICS
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_VDSP_FRAMEWORK
|
||||||
|
#define JUCE_USE_VDSP_FRAMEWORK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK
|
||||||
|
#include <Accelerate/Accelerate.h>
|
||||||
|
#else
|
||||||
|
#undef JUCE_USE_VDSP_FRAMEWORK
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_ARM_NEON
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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 "midi/juce_MidiBuffer.cpp"
|
||||||
|
#include "midi/juce_MidiFile.cpp"
|
||||||
|
#include "midi/juce_MidiKeyboardState.cpp"
|
||||||
|
#include "midi/juce_MidiMessage.cpp"
|
||||||
|
#include "midi/juce_MidiMessageSequence.cpp"
|
||||||
|
#include "midi/juce_MidiRPN.cpp"
|
||||||
|
#include "mpe/juce_MPEValue.cpp"
|
||||||
|
#include "mpe/juce_MPENote.cpp"
|
||||||
|
#include "mpe/juce_MPEZoneLayout.cpp"
|
||||||
|
#include "mpe/juce_MPEInstrument.cpp"
|
||||||
|
#include "mpe/juce_MPEMessages.cpp"
|
||||||
|
#include "mpe/juce_MPESynthesiserBase.cpp"
|
||||||
|
#include "mpe/juce_MPESynthesiserVoice.cpp"
|
||||||
|
#include "mpe/juce_MPESynthesiser.cpp"
|
||||||
|
#include "mpe/juce_MPEUtils.cpp"
|
||||||
|
#include "sources/juce_BufferingAudioSource.cpp"
|
||||||
|
#include "sources/juce_ChannelRemappingAudioSource.cpp"
|
||||||
|
#include "sources/juce_IIRFilterAudioSource.cpp"
|
||||||
|
#include "sources/juce_MemoryAudioSource.cpp"
|
||||||
|
#include "sources/juce_MixerAudioSource.cpp"
|
||||||
|
#include "sources/juce_ResamplingAudioSource.cpp"
|
||||||
|
#include "sources/juce_ReverbAudioSource.cpp"
|
||||||
|
#include "sources/juce_ToneGeneratorAudioSource.cpp"
|
||||||
|
#include "synthesisers/juce_Synthesiser.cpp"
|
119
modules/juce_audio_basics/juce_audio_basics.h
Normal file
119
modules/juce_audio_basics/juce_audio_basics.h
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
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_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
|
||||||
|
|
||||||
|
dependencies: juce_core
|
||||||
|
OSXFrameworks: Accelerate
|
||||||
|
iOSFrameworks: Accelerate
|
||||||
|
|
||||||
|
END_JUCE_MODULE_DECLARATION
|
||||||
|
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define JUCE_AUDIO_BASICS_H_INCLUDED
|
||||||
|
|
||||||
|
#include <juce_core/juce_core.h>
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#undef Complex // apparently some C libraries actually define these symbols (!)
|
||||||
|
#undef Factor
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_MINGW && ! defined (__SSE2__)
|
||||||
|
#define JUCE_USE_SSE_INTRINSICS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_SSE_INTRINSICS
|
||||||
|
#define JUCE_USE_SSE_INTRINSICS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! JUCE_INTEL
|
||||||
|
#undef JUCE_USE_SSE_INTRINSICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON))
|
||||||
|
#define JUCE_USE_ARM_NEON 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR
|
||||||
|
#ifdef JUCE_USE_ARM_NEON
|
||||||
|
#undef JUCE_USE_ARM_NEON
|
||||||
|
#endif
|
||||||
|
#define JUCE_USE_ARM_NEON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#include "buffers/juce_AudioDataConverters.h"
|
||||||
|
#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 "midi/juce_MidiMessage.h"
|
||||||
|
#include "midi/juce_MidiBuffer.h"
|
||||||
|
#include "midi/juce_MidiMessageSequence.h"
|
||||||
|
#include "midi/juce_MidiFile.h"
|
||||||
|
#include "midi/juce_MidiKeyboardState.h"
|
||||||
|
#include "midi/juce_MidiRPN.h"
|
||||||
|
#include "mpe/juce_MPEValue.h"
|
||||||
|
#include "mpe/juce_MPENote.h"
|
||||||
|
#include "mpe/juce_MPEZoneLayout.h"
|
||||||
|
#include "mpe/juce_MPEInstrument.h"
|
||||||
|
#include "mpe/juce_MPEMessages.h"
|
||||||
|
#include "mpe/juce_MPESynthesiserBase.h"
|
||||||
|
#include "mpe/juce_MPESynthesiserVoice.h"
|
||||||
|
#include "mpe/juce_MPESynthesiser.h"
|
||||||
|
#include "mpe/juce_MPEUtils.h"
|
||||||
|
#include "sources/juce_AudioSource.h"
|
||||||
|
#include "sources/juce_PositionableAudioSource.h"
|
||||||
|
#include "sources/juce_BufferingAudioSource.h"
|
||||||
|
#include "sources/juce_ChannelRemappingAudioSource.h"
|
||||||
|
#include "sources/juce_IIRFilterAudioSource.h"
|
||||||
|
#include "sources/juce_MemoryAudioSource.h"
|
||||||
|
#include "sources/juce_MixerAudioSource.h"
|
||||||
|
#include "sources/juce_ResamplingAudioSource.h"
|
||||||
|
#include "sources/juce_ReverbAudioSource.h"
|
||||||
|
#include "sources/juce_ToneGeneratorAudioSource.h"
|
||||||
|
#include "synthesisers/juce_Synthesiser.h"
|
||||||
|
#include "audio_play_head/juce_AudioPlayHead.h"
|
23
modules/juce_audio_basics/juce_audio_basics.mm
Normal file
23
modules/juce_audio_basics/juce_audio_basics.mm
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "juce_audio_basics.cpp"
|
230
modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
Normal file
230
modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace MidiBufferHelpers
|
||||||
|
{
|
||||||
|
inline int getEventTime (const void* const d) noexcept
|
||||||
|
{
|
||||||
|
return readUnaligned<int32> (d);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16 getEventDataSize (const void* const d) noexcept
|
||||||
|
{
|
||||||
|
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16 getEventTotalSize (const void* const d) noexcept
|
||||||
|
{
|
||||||
|
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept
|
||||||
|
{
|
||||||
|
unsigned int byte = (unsigned int) *data;
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
if (byte == 0xf0 || byte == 0xf7)
|
||||||
|
{
|
||||||
|
const uint8* d = data + 1;
|
||||||
|
|
||||||
|
while (d < data + maxBytes)
|
||||||
|
if (*d++ == 0xf7)
|
||||||
|
break;
|
||||||
|
|
||||||
|
size = (int) (d - data);
|
||||||
|
}
|
||||||
|
else if (byte == 0xff)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n);
|
||||||
|
size = jmin (maxBytes, n + 2 + bytesLeft);
|
||||||
|
}
|
||||||
|
else if (byte >= 0x80)
|
||||||
|
{
|
||||||
|
size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte));
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept
|
||||||
|
{
|
||||||
|
while (d < endData && getEventTime (d) <= samplePosition)
|
||||||
|
d += getEventTotalSize (d);
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiBuffer::MidiBuffer() noexcept {}
|
||||||
|
MidiBuffer::~MidiBuffer() {}
|
||||||
|
|
||||||
|
MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {}
|
||||||
|
|
||||||
|
MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept
|
||||||
|
{
|
||||||
|
data = other.data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept
|
||||||
|
{
|
||||||
|
addEvent (message, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); }
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1);
|
||||||
|
uint8* const 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)
|
||||||
|
{
|
||||||
|
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber)
|
||||||
|
{
|
||||||
|
const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (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());
|
||||||
|
|
||||||
|
data.insertMultiple (offset, 0, (int) newItemSize);
|
||||||
|
|
||||||
|
uint8* const d = data.begin() + offset;
|
||||||
|
writeUnaligned<int32> (d, sampleNumber);
|
||||||
|
writeUnaligned<uint16> (d + 4, static_cast<uint16> (numBytes));
|
||||||
|
memcpy (d + 6, newData, (size_t) numBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
|
||||||
|
const int startSample,
|
||||||
|
const int numSamples,
|
||||||
|
const int sampleDeltaToAdd)
|
||||||
|
{
|
||||||
|
Iterator i (otherBuffer);
|
||||||
|
i.setNextSamplePosition (startSample);
|
||||||
|
|
||||||
|
const uint8* eventData;
|
||||||
|
int eventSize, position;
|
||||||
|
|
||||||
|
while (i.getNextEvent (eventData, eventSize, position)
|
||||||
|
&& (position < startSample + numSamples || numSamples < 0))
|
||||||
|
{
|
||||||
|
addEvent (eventData, eventSize, position + sampleDeltaToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiBuffer::getNumEvents() const noexcept
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
const uint8* const end = data.end();
|
||||||
|
|
||||||
|
for (const uint8* d = data.begin(); d < end; ++n)
|
||||||
|
d += MidiBufferHelpers::getEventTotalSize (d);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiBuffer::getFirstEventTime() const noexcept
|
||||||
|
{
|
||||||
|
return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiBuffer::getLastEventTime() const noexcept
|
||||||
|
{
|
||||||
|
if (data.size() == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const uint8* const endData = data.end();
|
||||||
|
|
||||||
|
for (const uint8* d = data.begin();;)
|
||||||
|
{
|
||||||
|
const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d);
|
||||||
|
|
||||||
|
if (nextOne >= endData)
|
||||||
|
return MidiBufferHelpers::getEventTime (d);
|
||||||
|
|
||||||
|
d = nextOne;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept
|
||||||
|
: buffer (b), data (b.data.begin())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer::Iterator::~Iterator() noexcept{}
|
||||||
|
|
||||||
|
void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept
|
||||||
|
{
|
||||||
|
data = buffer.data.begin();
|
||||||
|
const uint8* const 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
|
||||||
|
{
|
||||||
|
if (data >= buffer.data.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
samplePosition = MidiBufferHelpers::getEventTime (data);
|
||||||
|
const int itemSize = MidiBufferHelpers::getEventDataSize (data);
|
||||||
|
numBytes = itemSize;
|
||||||
|
midiData = data + sizeof (int32) + sizeof (uint16);
|
||||||
|
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept
|
||||||
|
{
|
||||||
|
if (data >= buffer.data.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
samplePosition = MidiBufferHelpers::getEventTime (data);
|
||||||
|
const int itemSize = MidiBufferHelpers::getEventDataSize (data);
|
||||||
|
result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition);
|
||||||
|
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
237
modules/juce_audio_basics/midi/juce_MidiBuffer.h
Normal file
237
modules/juce_audio_basics/midi/juce_MidiBuffer.h
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Holds a sequence of time-stamped midi events.
|
||||||
|
|
||||||
|
Analogous to the AudioBuffer, this holds a set of midi events with
|
||||||
|
integer time-stamps. The buffer is kept sorted in order of the time-stamps.
|
||||||
|
|
||||||
|
If you're working with a sequence of midi events that may need to be manipulated
|
||||||
|
or read/written to a midi file, then MidiMessageSequence is probably a more
|
||||||
|
appropriate container. MidiBuffer is designed for lower-level streams of raw
|
||||||
|
midi data.
|
||||||
|
|
||||||
|
@see MidiMessage
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates an empty MidiBuffer. */
|
||||||
|
MidiBuffer() noexcept;
|
||||||
|
|
||||||
|
/** Creates a MidiBuffer containing a single midi message. */
|
||||||
|
explicit MidiBuffer (const MidiMessage& message) noexcept;
|
||||||
|
|
||||||
|
/** Creates a copy of another MidiBuffer. */
|
||||||
|
MidiBuffer (const MidiBuffer&) noexcept;
|
||||||
|
|
||||||
|
/** Makes a copy of another MidiBuffer. */
|
||||||
|
MidiBuffer& operator= (const MidiBuffer&) noexcept;
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
~MidiBuffer();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Removes all events from the buffer. */
|
||||||
|
void clear() noexcept;
|
||||||
|
|
||||||
|
/** Removes all events between two times from the buffer.
|
||||||
|
|
||||||
|
All events for which (start <= event position < start + numSamples) will
|
||||||
|
be removed.
|
||||||
|
*/
|
||||||
|
void clear (int start, int numSamples);
|
||||||
|
|
||||||
|
/** Returns true if the buffer is empty.
|
||||||
|
To actually retrieve the events, use a MidiBuffer::Iterator object
|
||||||
|
*/
|
||||||
|
bool isEmpty() const noexcept;
|
||||||
|
|
||||||
|
/** Counts the number of events in the buffer.
|
||||||
|
|
||||||
|
This is actually quite a slow operation, as it has to iterate through all
|
||||||
|
the events, so you might prefer to call isEmpty() if that's all you need
|
||||||
|
to know.
|
||||||
|
*/
|
||||||
|
int getNumEvents() const noexcept;
|
||||||
|
|
||||||
|
/** Adds an event to the buffer.
|
||||||
|
|
||||||
|
The sample number will be used to determine the position of the event in
|
||||||
|
the buffer, which is always kept sorted. The MidiMessage's timestamp is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
If an event is added whose sample position is the same as one or more events
|
||||||
|
already in the buffer, the new event will be placed after the existing ones.
|
||||||
|
|
||||||
|
To retrieve events, use a MidiBuffer::Iterator object
|
||||||
|
*/
|
||||||
|
void addEvent (const MidiMessage& midiMessage, int sampleNumber);
|
||||||
|
|
||||||
|
/** Adds an event to the buffer from raw midi data.
|
||||||
|
|
||||||
|
The sample number will be used to determine the position of the event in
|
||||||
|
the buffer, which is always kept sorted.
|
||||||
|
|
||||||
|
If an event is added whose sample position is the same as one or more events
|
||||||
|
already in the buffer, the new event will be placed after the existing ones.
|
||||||
|
|
||||||
|
The event data will be inspected to calculate the number of bytes in length that
|
||||||
|
the midi event really takes up, so maxBytesOfMidiData may be longer than the data
|
||||||
|
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes,
|
||||||
|
it'll actually only store 3 bytes. If the midi data is invalid, it might not
|
||||||
|
add an event at all.
|
||||||
|
|
||||||
|
To retrieve events, use a MidiBuffer::Iterator object
|
||||||
|
*/
|
||||||
|
void addEvent (const void* rawMidiData,
|
||||||
|
int maxBytesOfMidiData,
|
||||||
|
int sampleNumber);
|
||||||
|
|
||||||
|
/** Adds some events from another buffer to this one.
|
||||||
|
|
||||||
|
@param otherBuffer the buffer containing the events you want to add
|
||||||
|
@param startSample the lowest sample number in the source buffer for which
|
||||||
|
events should be added. Any source events whose timestamp is
|
||||||
|
less than this will be ignored
|
||||||
|
@param numSamples the valid range of samples from the source buffer for which
|
||||||
|
events should be added - i.e. events in the source buffer whose
|
||||||
|
timestamp is greater than or equal to (startSample + numSamples)
|
||||||
|
will be ignored. If this value is less than 0, all events after
|
||||||
|
startSample will be taken.
|
||||||
|
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events
|
||||||
|
that are added to this buffer
|
||||||
|
*/
|
||||||
|
void addEvents (const MidiBuffer& otherBuffer,
|
||||||
|
int startSample,
|
||||||
|
int numSamples,
|
||||||
|
int sampleDeltaToAdd);
|
||||||
|
|
||||||
|
/** Returns the sample number of the first event in the buffer.
|
||||||
|
If the buffer's empty, this will just return 0.
|
||||||
|
*/
|
||||||
|
int getFirstEventTime() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the sample number of the last event in the buffer.
|
||||||
|
If the buffer's empty, this will just return 0.
|
||||||
|
*/
|
||||||
|
int getLastEventTime() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Exchanges the contents of this buffer with another one.
|
||||||
|
|
||||||
|
This is a quick operation, because no memory allocating or copying is done, it
|
||||||
|
just swaps the internal state of the two buffers.
|
||||||
|
*/
|
||||||
|
void swapWith (MidiBuffer&) noexcept;
|
||||||
|
|
||||||
|
/** Preallocates some memory for the buffer to use.
|
||||||
|
This helps to avoid needing to reallocate space when the buffer has messages
|
||||||
|
added to it.
|
||||||
|
*/
|
||||||
|
void ensureSize (size_t minimumNumBytes);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Used to iterate through the events in a MidiBuffer.
|
||||||
|
|
||||||
|
Note that altering the buffer while an iterator is using it will produce
|
||||||
|
undefined behaviour.
|
||||||
|
|
||||||
|
@see MidiBuffer
|
||||||
|
*/
|
||||||
|
class JUCE_API Iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates an Iterator for this MidiBuffer. */
|
||||||
|
Iterator (const MidiBuffer&) noexcept;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Repositions the iterator so that the next event retrieved will be the first
|
||||||
|
one whose sample position is at greater than or equal to the given position.
|
||||||
|
*/
|
||||||
|
void setNextSamplePosition (int samplePosition) noexcept;
|
||||||
|
|
||||||
|
/** Retrieves a copy of the next event from the buffer.
|
||||||
|
|
||||||
|
@param result on return, this will be the message. The MidiMessage's timestamp
|
||||||
|
is set to the same value as samplePosition.
|
||||||
|
@param samplePosition on return, this will be the position of the event, as a
|
||||||
|
sample index in the buffer
|
||||||
|
@returns true if an event was found, or false if the iterator has reached
|
||||||
|
the end of the buffer
|
||||||
|
*/
|
||||||
|
bool getNextEvent (MidiMessage& result,
|
||||||
|
int& samplePosition) noexcept;
|
||||||
|
|
||||||
|
/** Retrieves the next event from the buffer.
|
||||||
|
|
||||||
|
@param midiData on return, this pointer will be set to a block of data containing
|
||||||
|
the midi message. Note that to make it fast, this is a pointer
|
||||||
|
directly into the MidiBuffer's internal data, so is only valid
|
||||||
|
temporarily until the MidiBuffer is altered.
|
||||||
|
@param numBytesOfMidiData on return, this is the number of bytes of data used by the
|
||||||
|
midi message
|
||||||
|
@param samplePosition on return, this will be the position of the event, as a
|
||||||
|
sample index in the buffer
|
||||||
|
@returns true if an event was found, or false if the iterator has reached
|
||||||
|
the end of the buffer
|
||||||
|
*/
|
||||||
|
bool getNextEvent (const uint8* &midiData,
|
||||||
|
int& numBytesOfMidiData,
|
||||||
|
int& samplePosition) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
const MidiBuffer& buffer;
|
||||||
|
const uint8* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The raw data holding this buffer.
|
||||||
|
Obviously access to this data is provided at your own risk. Its internal format could
|
||||||
|
change in future, so don't write code that relies on it!
|
||||||
|
*/
|
||||||
|
Array<uint8> data;
|
||||||
|
|
||||||
|
private:
|
||||||
|
JUCE_LEAK_DETECTOR (MidiBuffer)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
443
modules/juce_audio_basics/midi/juce_MidiFile.cpp
Normal file
443
modules/juce_audio_basics/midi/juce_MidiFile.cpp
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace MidiFileHelpers
|
||||||
|
{
|
||||||
|
static void writeVariableLengthInt (OutputStream& out, uint32 v)
|
||||||
|
{
|
||||||
|
auto buffer = v & 0x7f;
|
||||||
|
|
||||||
|
while ((v >>= 7) != 0)
|
||||||
|
{
|
||||||
|
buffer <<= 8;
|
||||||
|
buffer |= ((v & 0x7f) | 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
out.writeByte ((char) buffer);
|
||||||
|
|
||||||
|
if (buffer & 0x80)
|
||||||
|
buffer >>= 8;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
|
||||||
|
{
|
||||||
|
auto ch = ByteOrder::bigEndianInt (data);
|
||||||
|
data += 4;
|
||||||
|
|
||||||
|
if (ch != ByteOrder::bigEndianInt ("MThd"))
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
if (ch == ByteOrder::bigEndianInt ("RIFF"))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
ch = ByteOrder::bigEndianInt (data);
|
||||||
|
data += 4;
|
||||||
|
|
||||||
|
if (ch == ByteOrder::bigEndianInt ("MThd"))
|
||||||
|
{
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ok)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bytesRemaining = ByteOrder::bigEndianInt (data);
|
||||||
|
data += 4;
|
||||||
|
fileType = (short) ByteOrder::bigEndianShort (data);
|
||||||
|
data += 2;
|
||||||
|
numberOfTracks = (short) ByteOrder::bigEndianShort (data);
|
||||||
|
data += 2;
|
||||||
|
timeFormat = (short) ByteOrder::bigEndianShort (data);
|
||||||
|
data += 2;
|
||||||
|
bytesRemaining -= 6;
|
||||||
|
data += bytesRemaining;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double convertTicksToSeconds (double time,
|
||||||
|
const MidiMessageSequence& tempoEvents,
|
||||||
|
int timeFormat)
|
||||||
|
{
|
||||||
|
if (timeFormat < 0)
|
||||||
|
return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
|
||||||
|
|
||||||
|
double lastTime = 0, correctedTime = 0;
|
||||||
|
auto tickLen = 1.0 / (timeFormat & 0x7fff);
|
||||||
|
auto secsPerTick = 0.5 * tickLen;
|
||||||
|
auto numEvents = tempoEvents.getNumEvents();
|
||||||
|
|
||||||
|
for (int i = 0; i < numEvents; ++i)
|
||||||
|
{
|
||||||
|
auto& m = tempoEvents.getEventPointer(i)->message;
|
||||||
|
auto eventTime = m.getTimeStamp();
|
||||||
|
|
||||||
|
if (eventTime >= time)
|
||||||
|
break;
|
||||||
|
|
||||||
|
correctedTime += (eventTime - lastTime) * secsPerTick;
|
||||||
|
lastTime = eventTime;
|
||||||
|
|
||||||
|
if (m.isTempoMetaEvent())
|
||||||
|
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
|
||||||
|
|
||||||
|
while (i + 1 < numEvents)
|
||||||
|
{
|
||||||
|
auto& m2 = tempoEvents.getEventPointer(i + 1)->message;
|
||||||
|
|
||||||
|
if (m2.getTimeStamp() != eventTime)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (m2.isTempoMetaEvent())
|
||||||
|
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return correctedTime + (time - lastTime) * secsPerTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename MethodType>
|
||||||
|
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
|
||||||
|
MidiMessageSequence& results,
|
||||||
|
MethodType method)
|
||||||
|
{
|
||||||
|
for (auto* track : tracks)
|
||||||
|
{
|
||||||
|
auto numEvents = track->getNumEvents();
|
||||||
|
|
||||||
|
for (int j = 0; j < numEvents; ++j)
|
||||||
|
{
|
||||||
|
auto& m = track->getEventPointer(j)->message;
|
||||||
|
|
||||||
|
if ((m.*method)())
|
||||||
|
results.addEvent (m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
|
||||||
|
MidiFile::~MidiFile() {}
|
||||||
|
|
||||||
|
MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
|
||||||
|
{
|
||||||
|
tracks.addCopiesOf (other.tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiFile& MidiFile::operator= (const MidiFile& other)
|
||||||
|
{
|
||||||
|
tracks.clear();
|
||||||
|
tracks.addCopiesOf (other.tracks);
|
||||||
|
timeFormat = other.timeFormat;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiFile::MidiFile (MidiFile&& other)
|
||||||
|
: tracks (static_cast<OwnedArray<MidiMessageSequence>&&> (other.tracks)),
|
||||||
|
timeFormat (other.timeFormat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiFile& MidiFile::operator= (MidiFile&& other)
|
||||||
|
{
|
||||||
|
tracks = static_cast<OwnedArray<MidiMessageSequence>&&> (other.tracks);
|
||||||
|
timeFormat = other.timeFormat;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::clear()
|
||||||
|
{
|
||||||
|
tracks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
int MidiFile::getNumTracks() const noexcept
|
||||||
|
{
|
||||||
|
return tracks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
|
||||||
|
{
|
||||||
|
return tracks[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
|
||||||
|
{
|
||||||
|
tracks.add (new MidiMessageSequence (trackSequence));
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
short MidiFile::getTimeFormat() const noexcept
|
||||||
|
{
|
||||||
|
return timeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::setTicksPerQuarterNote (int ticks) noexcept
|
||||||
|
{
|
||||||
|
timeFormat = (short) ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept
|
||||||
|
{
|
||||||
|
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const
|
||||||
|
{
|
||||||
|
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const
|
||||||
|
{
|
||||||
|
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const
|
||||||
|
{
|
||||||
|
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
double MidiFile::getLastTimestamp() const
|
||||||
|
{
|
||||||
|
double t = 0.0;
|
||||||
|
|
||||||
|
for (auto* ms : tracks)
|
||||||
|
t = jmax (t, ms->getEndTime());
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MidiFile::readFrom (InputStream& sourceStream)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
MemoryBlock data;
|
||||||
|
|
||||||
|
const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
|
||||||
|
|
||||||
|
// (put a sanity-check on the file size, as midi files are generally small)
|
||||||
|
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
|
||||||
|
{
|
||||||
|
auto size = data.getSize();
|
||||||
|
auto d = static_cast<const uint8*> (data.getData());
|
||||||
|
short fileType, expectedTracks;
|
||||||
|
|
||||||
|
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
|
||||||
|
{
|
||||||
|
size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
|
||||||
|
|
||||||
|
int track = 0;
|
||||||
|
|
||||||
|
while (size > 0 && track < expectedTracks)
|
||||||
|
{
|
||||||
|
auto chunkType = (int) ByteOrder::bigEndianInt (d);
|
||||||
|
d += 4;
|
||||||
|
auto chunkSize = (int) ByteOrder::bigEndianInt (d);
|
||||||
|
d += 4;
|
||||||
|
|
||||||
|
if (chunkSize <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
|
||||||
|
readNextTrack (d, chunkSize);
|
||||||
|
|
||||||
|
size -= (size_t) chunkSize + 8;
|
||||||
|
d += chunkSize;
|
||||||
|
++track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiFile::readNextTrack (const uint8* data, int size)
|
||||||
|
{
|
||||||
|
double time = 0;
|
||||||
|
uint8 lastStatusByte = 0;
|
||||||
|
|
||||||
|
MidiMessageSequence result;
|
||||||
|
|
||||||
|
while (size > 0)
|
||||||
|
{
|
||||||
|
int bytesUsed;
|
||||||
|
auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
|
||||||
|
data += bytesUsed;
|
||||||
|
size -= bytesUsed;
|
||||||
|
time += delay;
|
||||||
|
|
||||||
|
int messSize = 0;
|
||||||
|
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
|
||||||
|
|
||||||
|
if (messSize <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
size -= messSize;
|
||||||
|
data += messSize;
|
||||||
|
|
||||||
|
result.addEvent (mm);
|
||||||
|
|
||||||
|
auto firstByte = *(mm.getRawData());
|
||||||
|
|
||||||
|
if ((firstByte & 0xf0) != 0xf0)
|
||||||
|
lastStatusByte = firstByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort so that we put all the note-offs before note-ons that have the same time
|
||||||
|
std::stable_sort (result.list.begin(), result.list.end(),
|
||||||
|
[] (const MidiMessageSequence::MidiEventHolder* a,
|
||||||
|
const MidiMessageSequence::MidiEventHolder* b)
|
||||||
|
{
|
||||||
|
auto t1 = a->message.getTimeStamp();
|
||||||
|
auto t2 = b->message.getTimeStamp();
|
||||||
|
|
||||||
|
if (t1 < t2) return true;
|
||||||
|
if (t2 < t1) return false;
|
||||||
|
|
||||||
|
return a->message.isNoteOff() && b->message.isNoteOn();
|
||||||
|
});
|
||||||
|
|
||||||
|
addTrack (result);
|
||||||
|
tracks.getLast()->updateMatchedPairs();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiFile::convertTimestampTicksToSeconds()
|
||||||
|
{
|
||||||
|
MidiMessageSequence tempoEvents;
|
||||||
|
findAllTempoEvents (tempoEvents);
|
||||||
|
findAllTimeSigEvents (tempoEvents);
|
||||||
|
|
||||||
|
if (timeFormat != 0)
|
||||||
|
{
|
||||||
|
for (auto* ms : tracks)
|
||||||
|
{
|
||||||
|
for (int j = ms->getNumEvents(); --j >= 0;)
|
||||||
|
{
|
||||||
|
auto& m = ms->getEventPointer(j)->message;
|
||||||
|
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MidiFile::writeTo (OutputStream& out, int midiFileType)
|
||||||
|
{
|
||||||
|
jassert (midiFileType >= 0 && midiFileType <= 2);
|
||||||
|
|
||||||
|
if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
|
||||||
|
if (! out.writeIntBigEndian (6)) return false;
|
||||||
|
if (! out.writeShortBigEndian ((short) midiFileType)) return false;
|
||||||
|
if (! out.writeShortBigEndian ((short) tracks.size())) return false;
|
||||||
|
if (! out.writeShortBigEndian (timeFormat)) return false;
|
||||||
|
|
||||||
|
for (auto* ms : tracks)
|
||||||
|
if (! writeTrack (out, *ms))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms)
|
||||||
|
{
|
||||||
|
MemoryOutputStream out;
|
||||||
|
|
||||||
|
int lastTick = 0;
|
||||||
|
uint8 lastStatusByte = 0;
|
||||||
|
bool endOfTrackEventWritten = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < ms.getNumEvents(); ++i)
|
||||||
|
{
|
||||||
|
auto& mm = ms.getEventPointer(i)->message;
|
||||||
|
|
||||||
|
if (mm.isEndOfTrackMetaEvent())
|
||||||
|
endOfTrackEventWritten = true;
|
||||||
|
|
||||||
|
auto tick = roundToInt (mm.getTimeStamp());
|
||||||
|
auto delta = jmax (0, tick - lastTick);
|
||||||
|
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
|
||||||
|
lastTick = tick;
|
||||||
|
|
||||||
|
auto* data = mm.getRawData();
|
||||||
|
auto dataSize = mm.getRawDataSize();
|
||||||
|
auto statusByte = data[0];
|
||||||
|
|
||||||
|
if (statusByte == lastStatusByte
|
||||||
|
&& (statusByte & 0xf0) != 0xf0
|
||||||
|
&& dataSize > 1
|
||||||
|
&& i > 0)
|
||||||
|
{
|
||||||
|
++data;
|
||||||
|
--dataSize;
|
||||||
|
}
|
||||||
|
else if (statusByte == 0xf0) // Write sysex message with length bytes.
|
||||||
|
{
|
||||||
|
out.writeByte ((char) statusByte);
|
||||||
|
|
||||||
|
++data;
|
||||||
|
--dataSize;
|
||||||
|
|
||||||
|
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write (data, (size_t) dataSize);
|
||||||
|
lastStatusByte = statusByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! endOfTrackEventWritten)
|
||||||
|
{
|
||||||
|
out.writeByte (0); // (tick delta)
|
||||||
|
auto m = MidiMessage::endOfTrack();
|
||||||
|
out.write (m.getRawData(), (size_t) m.getRawDataSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
|
||||||
|
if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
|
||||||
|
|
||||||
|
mainOut << out;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
189
modules/juce_audio_basics/midi/juce_MidiFile.h
Normal file
189
modules/juce_audio_basics/midi/juce_MidiFile.h
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Reads/writes standard midi format files.
|
||||||
|
|
||||||
|
To read a midi file, create a MidiFile object and call its readFrom() method. You
|
||||||
|
can then get the individual midi tracks from it using the getTrack() method.
|
||||||
|
|
||||||
|
To write a file, create a MidiFile object, add some MidiMessageSequence objects
|
||||||
|
to it using the addTrack() method, and then call its writeTo() method to stream
|
||||||
|
it out.
|
||||||
|
|
||||||
|
@see MidiMessageSequence
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates an empty MidiFile object. */
|
||||||
|
MidiFile();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiFile();
|
||||||
|
|
||||||
|
/** Creates a copy of another MidiFile. */
|
||||||
|
MidiFile (const MidiFile&);
|
||||||
|
|
||||||
|
/** Copies from another MidiFile object */
|
||||||
|
MidiFile& operator= (const MidiFile&);
|
||||||
|
|
||||||
|
/** Creates a copy of another MidiFile. */
|
||||||
|
MidiFile (MidiFile&&);
|
||||||
|
|
||||||
|
/** Copies from another MidiFile object */
|
||||||
|
MidiFile& operator= (MidiFile&&);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the number of tracks in the file.
|
||||||
|
@see getTrack, addTrack
|
||||||
|
*/
|
||||||
|
int getNumTracks() const noexcept;
|
||||||
|
|
||||||
|
/** Returns a pointer to one of the tracks in the file.
|
||||||
|
@returns a pointer to the track, or nullptr if the index is out-of-range
|
||||||
|
@see getNumTracks, addTrack
|
||||||
|
*/
|
||||||
|
const MidiMessageSequence* getTrack (int index) const noexcept;
|
||||||
|
|
||||||
|
/** Adds a midi track to the file.
|
||||||
|
This will make its own internal copy of the sequence that is passed-in.
|
||||||
|
@see getNumTracks, getTrack
|
||||||
|
*/
|
||||||
|
void addTrack (const MidiMessageSequence& trackSequence);
|
||||||
|
|
||||||
|
/** Removes all midi tracks from the file.
|
||||||
|
@see getNumTracks
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/** Returns the raw time format code that will be written to a stream.
|
||||||
|
|
||||||
|
After reading a midi file, this method will return the time-format that
|
||||||
|
was read from the file's header. It can be changed using the setTicksPerQuarterNote()
|
||||||
|
or setSmpteTimeFormat() methods.
|
||||||
|
|
||||||
|
If the value returned is positive, it indicates the number of midi ticks
|
||||||
|
per quarter-note - see setTicksPerQuarterNote().
|
||||||
|
|
||||||
|
It it's negative, the upper byte indicates the frames-per-second (but negative), and
|
||||||
|
the lower byte is the number of ticks per frame - see setSmpteTimeFormat().
|
||||||
|
*/
|
||||||
|
short getTimeFormat() const noexcept;
|
||||||
|
|
||||||
|
/** Sets the time format to use when this file is written to a stream.
|
||||||
|
|
||||||
|
If this is called, the file will be written as bars/beats using the
|
||||||
|
specified resolution, rather than SMPTE absolute times, as would be
|
||||||
|
used if setSmpteTimeFormat() had been called instead.
|
||||||
|
|
||||||
|
@param ticksPerQuarterNote e.g. 96, 960
|
||||||
|
@see setSmpteTimeFormat
|
||||||
|
*/
|
||||||
|
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept;
|
||||||
|
|
||||||
|
/** Sets the time format to use when this file is written to a stream.
|
||||||
|
|
||||||
|
If this is called, the file will be written using absolute times, rather
|
||||||
|
than bars/beats as would be the case if setTicksPerBeat() had been called
|
||||||
|
instead.
|
||||||
|
|
||||||
|
@param framesPerSecond must be 24, 25, 29 or 30
|
||||||
|
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code),
|
||||||
|
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond
|
||||||
|
timing, setSmpteTimeFormat (25, 40)
|
||||||
|
@see setTicksPerBeat
|
||||||
|
*/
|
||||||
|
void setSmpteTimeFormat (int framesPerSecond,
|
||||||
|
int subframeResolution) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file.
|
||||||
|
Useful for finding the positions of all the tempo changes in a file.
|
||||||
|
@param tempoChangeEvents a list to which all the events will be added
|
||||||
|
*/
|
||||||
|
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const;
|
||||||
|
|
||||||
|
/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
|
||||||
|
Useful for finding the positions of all the tempo changes in a file.
|
||||||
|
@param timeSigEvents a list to which all the events will be added
|
||||||
|
*/
|
||||||
|
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const;
|
||||||
|
|
||||||
|
/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
|
||||||
|
@param keySigEvents a list to which all the events will be added
|
||||||
|
*/
|
||||||
|
void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const;
|
||||||
|
|
||||||
|
/** Returns the latest timestamp in any of the tracks.
|
||||||
|
(Useful for finding the length of the file).
|
||||||
|
*/
|
||||||
|
double getLastTimestamp() const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Reads a midi file format stream.
|
||||||
|
|
||||||
|
After calling this, you can get the tracks that were read from the file by using the
|
||||||
|
getNumTracks() and getTrack() methods.
|
||||||
|
|
||||||
|
The timestamps of the midi events in the tracks will represent their positions in
|
||||||
|
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds()
|
||||||
|
method.
|
||||||
|
|
||||||
|
@returns true if the stream was read successfully
|
||||||
|
*/
|
||||||
|
bool readFrom (InputStream& sourceStream);
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
@returns true if the operation succeeded.
|
||||||
|
*/
|
||||||
|
bool writeTo (OutputStream& destStream, int midiFileType = 1);
|
||||||
|
|
||||||
|
/** Converts the timestamp of all the midi events from midi ticks to seconds.
|
||||||
|
|
||||||
|
This will use the midi time format and tempo/time signature info in the
|
||||||
|
tracks to convert all the timestamps to absolute values in seconds.
|
||||||
|
*/
|
||||||
|
void convertTimestampTicksToSeconds();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OwnedArray<MidiMessageSequence> tracks;
|
||||||
|
short timeFormat;
|
||||||
|
|
||||||
|
void readNextTrack (const uint8*, int size);
|
||||||
|
bool writeTrack (OutputStream&, const MidiMessageSequence&);
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR (MidiFile)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
186
modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
Normal file
186
modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MidiKeyboardState::MidiKeyboardState()
|
||||||
|
{
|
||||||
|
zerostruct (noteStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiKeyboardState::~MidiKeyboardState()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiKeyboardState::reset()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
zerostruct (noteStates);
|
||||||
|
eventsToAdd.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept
|
||||||
|
{
|
||||||
|
jassert (midiChannel >= 0 && midiChannel <= 16);
|
||||||
|
|
||||||
|
return isPositiveAndBelow (n, 128)
|
||||||
|
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
|
||||||
|
{
|
||||||
|
return isPositiveAndBelow (n, 128)
|
||||||
|
&& (noteStates[n] & midiChannelMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||||
|
{
|
||||||
|
jassert (midiChannel >= 0 && midiChannel <= 16);
|
||||||
|
jassert (isPositiveAndBelow (midiNoteNumber, 128));
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (isPositiveAndBelow (midiNoteNumber, 128))
|
||||||
|
{
|
||||||
|
const int timeNow = (int) Time::getMillisecondCounter();
|
||||||
|
eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow);
|
||||||
|
eventsToAdd.clear (0, timeNow - 500);
|
||||||
|
|
||||||
|
noteOnInternal (midiChannel, midiNoteNumber, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||||
|
{
|
||||||
|
if (isPositiveAndBelow (midiNoteNumber, 128))
|
||||||
|
{
|
||||||
|
noteStates [midiNoteNumber] |= (1 << (midiChannel - 1));
|
||||||
|
|
||||||
|
for (int i = listeners.size(); --i >= 0;)
|
||||||
|
listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||||
|
{
|
||||||
|
const int timeNow = (int) Time::getMillisecondCounter();
|
||||||
|
eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow);
|
||||||
|
eventsToAdd.clear (0, timeNow - 500);
|
||||||
|
|
||||||
|
noteOffInternal (midiChannel, midiNoteNumber, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||||
|
{
|
||||||
|
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||||
|
{
|
||||||
|
noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1));
|
||||||
|
|
||||||
|
for (int i = listeners.size(); --i >= 0;)
|
||||||
|
listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::allNotesOff (const int midiChannel)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (midiChannel <= 0)
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 16; ++i)
|
||||||
|
allNotesOff (i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 128; ++i)
|
||||||
|
noteOff (midiChannel, i, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message)
|
||||||
|
{
|
||||||
|
if (message.isNoteOn())
|
||||||
|
{
|
||||||
|
noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
|
||||||
|
}
|
||||||
|
else if (message.isNoteOff())
|
||||||
|
{
|
||||||
|
noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
|
||||||
|
}
|
||||||
|
else if (message.isAllNotesOff())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 128; ++i)
|
||||||
|
noteOffInternal (message.getChannel(), i, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
|
||||||
|
const int startSample,
|
||||||
|
const int numSamples,
|
||||||
|
const bool injectIndirectEvents)
|
||||||
|
{
|
||||||
|
MidiBuffer::Iterator i (buffer);
|
||||||
|
MidiMessage message;
|
||||||
|
int time;
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
while (i.getNextEvent (message, time))
|
||||||
|
processNextMidiEvent (message);
|
||||||
|
|
||||||
|
if (injectIndirectEvents)
|
||||||
|
{
|
||||||
|
MidiBuffer::Iterator i2 (eventsToAdd);
|
||||||
|
const int firstEventToAdd = eventsToAdd.getFirstEventTime();
|
||||||
|
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
|
||||||
|
|
||||||
|
while (i2.getNextEvent (message, time))
|
||||||
|
{
|
||||||
|
const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor));
|
||||||
|
buffer.addEvent (message, startSample + pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsToAdd.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
listeners.addIfNotAlreadyThere (listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
listeners.removeFirstMatchingValue (listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
206
modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
Normal file
206
modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
class MidiKeyboardState;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Receives events from a MidiKeyboardState object.
|
||||||
|
|
||||||
|
@see MidiKeyboardState
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiKeyboardStateListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
MidiKeyboardStateListener() noexcept {}
|
||||||
|
virtual ~MidiKeyboardStateListener() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Called when one of the MidiKeyboardState's keys is pressed.
|
||||||
|
|
||||||
|
This will be called synchronously when the state is either processing a
|
||||||
|
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
|
||||||
|
when a note is being played with its MidiKeyboardState::noteOn() method.
|
||||||
|
|
||||||
|
Note that this callback could happen from an audio callback thread, so be
|
||||||
|
careful not to block, and avoid any UI activity in the callback.
|
||||||
|
*/
|
||||||
|
virtual void handleNoteOn (MidiKeyboardState* source,
|
||||||
|
int midiChannel, int midiNoteNumber, float velocity) = 0;
|
||||||
|
|
||||||
|
/** Called when one of the MidiKeyboardState's keys is released.
|
||||||
|
|
||||||
|
This will be called synchronously when the state is either processing a
|
||||||
|
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
|
||||||
|
when a note is being played with its MidiKeyboardState::noteOff() method.
|
||||||
|
|
||||||
|
Note that this callback could happen from an audio callback thread, so be
|
||||||
|
careful not to block, and avoid any UI activity in the callback.
|
||||||
|
*/
|
||||||
|
virtual void handleNoteOff (MidiKeyboardState* source,
|
||||||
|
int midiChannel, int midiNoteNumber, float velocity) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
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.
|
||||||
|
|
||||||
|
When keys go up or down, it can broadcast these events to listener objects.
|
||||||
|
|
||||||
|
It also allows key up/down events to be triggered with its noteOn() and noteOff()
|
||||||
|
methods, and midi messages for these events will be merged into the
|
||||||
|
midi stream that gets processed by processNextMidiBuffer().
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiKeyboardState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
MidiKeyboardState();
|
||||||
|
~MidiKeyboardState();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Resets the state of the object.
|
||||||
|
|
||||||
|
All internal data for all the channels is reset, but no events are sent as a
|
||||||
|
result.
|
||||||
|
|
||||||
|
If you want to release any keys that are currently down, and to send out note-up
|
||||||
|
midi messages for this, use the allNotesOff() method instead.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/** Returns true if the given midi key is currently held down for the given midi channel.
|
||||||
|
|
||||||
|
The channel number must be between 1 and 16. If you want to see if any notes are
|
||||||
|
on for a range of channels, use the isNoteOnForChannels() method.
|
||||||
|
*/
|
||||||
|
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if the given midi key is currently held down on any of a set of midi channels.
|
||||||
|
|
||||||
|
The channel mask has a bit set for each midi channel you want to test for - bit
|
||||||
|
0 = midi channel 1, bit 1 = midi channel 2, etc.
|
||||||
|
|
||||||
|
If a note is on for at least one of the specified channels, this returns true.
|
||||||
|
*/
|
||||||
|
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept;
|
||||||
|
|
||||||
|
/** Turns a specified note on.
|
||||||
|
|
||||||
|
This will cause a suitable midi note-on event to be injected into the midi buffer during the
|
||||||
|
next call to processNextMidiBuffer().
|
||||||
|
|
||||||
|
It will also trigger a synchronous callback to the listeners to tell them that the key has
|
||||||
|
gone down.
|
||||||
|
*/
|
||||||
|
void noteOn (int midiChannel, int midiNoteNumber, float velocity);
|
||||||
|
|
||||||
|
/** Turns a specified note off.
|
||||||
|
|
||||||
|
This will cause a suitable midi note-off event to be injected into the midi buffer during the
|
||||||
|
next call to processNextMidiBuffer().
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
void noteOff (int midiChannel, int midiNoteNumber, float velocity);
|
||||||
|
|
||||||
|
/** This will turn off any currently-down notes for the given midi channel.
|
||||||
|
|
||||||
|
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels.
|
||||||
|
|
||||||
|
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks
|
||||||
|
and events being added to the midi stream.
|
||||||
|
*/
|
||||||
|
void allNotesOff (int midiChannel);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Looks at a key-up/down event and uses it to update the state of this object.
|
||||||
|
|
||||||
|
To process a buffer full of midi messages, use the processNextMidiBuffer() method
|
||||||
|
instead.
|
||||||
|
*/
|
||||||
|
void processNextMidiEvent (const MidiMessage& message);
|
||||||
|
|
||||||
|
/** Scans a midi stream for up/down events and adds its own events to it.
|
||||||
|
|
||||||
|
This will look for any up/down events and use them to update the internal state,
|
||||||
|
synchronously making suitable callbacks to the listeners.
|
||||||
|
|
||||||
|
If injectIndirectEvents is true, then midi events to produce the recent noteOn()
|
||||||
|
and noteOff() calls will be added into the buffer.
|
||||||
|
|
||||||
|
Only the section of the buffer whose timestamps are between startSample and
|
||||||
|
(startSample + numSamples) will be affected, and any events added will be placed
|
||||||
|
between these times.
|
||||||
|
|
||||||
|
If you're going to use this method, you'll need to keep calling it regularly for
|
||||||
|
it to work satisfactorily.
|
||||||
|
|
||||||
|
To process a single midi event at a time, use the processNextMidiEvent() method
|
||||||
|
instead.
|
||||||
|
*/
|
||||||
|
void processNextMidiBuffer (MidiBuffer& buffer,
|
||||||
|
int startSample,
|
||||||
|
int numSamples,
|
||||||
|
bool injectIndirectEvents);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Registers a listener for callbacks when keys go up or down.
|
||||||
|
@see removeListener
|
||||||
|
*/
|
||||||
|
void addListener (MidiKeyboardStateListener* listener);
|
||||||
|
|
||||||
|
/** Deregisters a listener.
|
||||||
|
@see addListener
|
||||||
|
*/
|
||||||
|
void removeListener (MidiKeyboardStateListener* listener);
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
CriticalSection lock;
|
||||||
|
uint16 noteStates [128];
|
||||||
|
MidiBuffer eventsToAdd;
|
||||||
|
Array <MidiKeyboardStateListener*> listeners;
|
||||||
|
|
||||||
|
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity);
|
||||||
|
void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity);
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
1137
modules/juce_audio_basics/midi/juce_MidiMessage.cpp
Normal file
1137
modules/juce_audio_basics/midi/juce_MidiMessage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
952
modules/juce_audio_basics/midi/juce_MidiMessage.h
Normal file
952
modules/juce_audio_basics/midi/juce_MidiMessage.h
Normal file
|
@ -0,0 +1,952 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Encapsulates a MIDI message.
|
||||||
|
|
||||||
|
@see MidiMessageSequence, MidiOutput, MidiInput
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a 3-byte short midi message.
|
||||||
|
|
||||||
|
@param byte1 message byte 1
|
||||||
|
@param byte2 message byte 2
|
||||||
|
@param byte3 message byte 3
|
||||||
|
@param timeStamp the time to give the midi message - this value doesn't
|
||||||
|
use any particular units, so will be application-specific
|
||||||
|
*/
|
||||||
|
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept;
|
||||||
|
|
||||||
|
/** Creates a 2-byte short midi message.
|
||||||
|
|
||||||
|
@param byte1 message byte 1
|
||||||
|
@param byte2 message byte 2
|
||||||
|
@param timeStamp the time to give the midi message - this value doesn't
|
||||||
|
use any particular units, so will be application-specific
|
||||||
|
*/
|
||||||
|
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept;
|
||||||
|
|
||||||
|
/** Creates a 1-byte short midi message.
|
||||||
|
|
||||||
|
@param byte1 message byte 1
|
||||||
|
@param timeStamp the time to give the midi message - this value doesn't
|
||||||
|
use any particular units, so will be application-specific
|
||||||
|
*/
|
||||||
|
MidiMessage (int byte1, double timeStamp = 0) noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi message from a list of bytes. */
|
||||||
|
template <typename... Data>
|
||||||
|
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes))
|
||||||
|
{
|
||||||
|
// this checks that the length matches the data..
|
||||||
|
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size);
|
||||||
|
|
||||||
|
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... };
|
||||||
|
memcpy (allocateSpace (size), data, (size_t) size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a midi message from a block of data. */
|
||||||
|
MidiMessage (const void* data, int numBytes, double timeStamp = 0);
|
||||||
|
|
||||||
|
/** Reads the next midi message from some data.
|
||||||
|
|
||||||
|
This will read as many bytes from a data stream as it needs to make a
|
||||||
|
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 sysexHasEmbeddedLength when reading sysexes, this flag indicates whether
|
||||||
|
to expect the data to begin with a variable-length field
|
||||||
|
indicating its size
|
||||||
|
*/
|
||||||
|
MidiMessage (const void* data, int maxBytesToUse,
|
||||||
|
int& numBytesUsed, uint8 lastStatusByte,
|
||||||
|
double timeStamp = 0,
|
||||||
|
bool sysexHasEmbeddedLength = true);
|
||||||
|
|
||||||
|
/** Creates an active-sense message.
|
||||||
|
Since the MidiMessage has to contain a valid message, this default constructor
|
||||||
|
just initialises it with an empty sysex message.
|
||||||
|
*/
|
||||||
|
MidiMessage() noexcept;
|
||||||
|
|
||||||
|
/** Creates a copy of another midi message. */
|
||||||
|
MidiMessage (const MidiMessage&);
|
||||||
|
|
||||||
|
/** Creates a copy of another midi message, with a different timestamp. */
|
||||||
|
MidiMessage (const MidiMessage&, double newTimeStamp);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiMessage() noexcept;
|
||||||
|
|
||||||
|
/** Copies this message from another one. */
|
||||||
|
MidiMessage& operator= (const MidiMessage& other);
|
||||||
|
|
||||||
|
/** Move constructor */
|
||||||
|
MidiMessage (MidiMessage&&) noexcept;
|
||||||
|
|
||||||
|
/** Move assignment operator */
|
||||||
|
MidiMessage& operator= (MidiMessage&&) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns a pointer to the raw midi data.
|
||||||
|
@see getRawDataSize
|
||||||
|
*/
|
||||||
|
const uint8* getRawData() const noexcept { return getData(); }
|
||||||
|
|
||||||
|
/** Returns the number of bytes of data in the message.
|
||||||
|
@see getRawData
|
||||||
|
*/
|
||||||
|
int getRawDataSize() const noexcept { return size; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns a human-readable description of the midi message as a string,
|
||||||
|
for example "Note On C#3 Velocity 120 Channel 1".
|
||||||
|
*/
|
||||||
|
String getDescription() const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the timestamp associated with this message.
|
||||||
|
|
||||||
|
The exact meaning of this time and its units will vary, as messages are used in
|
||||||
|
a variety of different contexts.
|
||||||
|
|
||||||
|
If you're getting the message from a midi file, this could be a time in seconds, or
|
||||||
|
a number of ticks - see MidiFile::convertTimestampTicksToSeconds().
|
||||||
|
|
||||||
|
If the message is being used in a MidiBuffer, it might indicate the number of
|
||||||
|
audio samples from the start of the buffer.
|
||||||
|
|
||||||
|
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage()
|
||||||
|
for details of the way that it initialises this value.
|
||||||
|
|
||||||
|
@see setTimeStamp, addToTimeStamp
|
||||||
|
*/
|
||||||
|
double getTimeStamp() const noexcept { return timeStamp; }
|
||||||
|
|
||||||
|
/** Changes the message's associated timestamp.
|
||||||
|
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
|
||||||
|
@see addToTimeStamp, getTimeStamp
|
||||||
|
*/
|
||||||
|
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; }
|
||||||
|
|
||||||
|
/** Adds a value to the message's timestamp.
|
||||||
|
The units for the timestamp will be application-specific.
|
||||||
|
*/
|
||||||
|
void addToTimeStamp (double delta) noexcept { timeStamp += delta; }
|
||||||
|
|
||||||
|
/** Return a copy of this message with a new timestamp.
|
||||||
|
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
|
||||||
|
*/
|
||||||
|
MidiMessage withTimeStamp (double newTimestamp) const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the midi channel associated with the message.
|
||||||
|
|
||||||
|
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g.
|
||||||
|
if it's a sysex)
|
||||||
|
@see isForChannel, setChannel
|
||||||
|
*/
|
||||||
|
int getChannel() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if the message applies to the given midi channel.
|
||||||
|
|
||||||
|
@param channelNumber the channel number to look for, in the range 1 to 16
|
||||||
|
@see getChannel, setChannel
|
||||||
|
*/
|
||||||
|
bool isForChannel (int channelNumber) const noexcept;
|
||||||
|
|
||||||
|
/** Changes the message's midi channel.
|
||||||
|
This won't do anything for non-channel messages like sysexes.
|
||||||
|
@param newChannelNumber the channel number to change it to, in the range 1 to 16
|
||||||
|
*/
|
||||||
|
void setChannel (int newChannelNumber) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a system-exclusive message.
|
||||||
|
*/
|
||||||
|
bool isSysEx() const noexcept;
|
||||||
|
|
||||||
|
/** Returns a pointer to the sysex data inside the message.
|
||||||
|
If this event isn't a sysex event, it'll return 0.
|
||||||
|
@see getSysExDataSize
|
||||||
|
*/
|
||||||
|
const uint8* getSysExData() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the size of the sysex data.
|
||||||
|
This value excludes the 0xf0 header byte and the 0xf7 at the end.
|
||||||
|
@see getSysExData
|
||||||
|
*/
|
||||||
|
int getSysExDataSize() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this message is a 'key-down' event.
|
||||||
|
|
||||||
|
@param returnTrueForVelocity0 if true, then if this event is a note-on with
|
||||||
|
velocity 0, it will still be considered to be a note-on and the
|
||||||
|
method will return true. If returnTrueForVelocity0 is false, then
|
||||||
|
if this is a note-on event with velocity 0, it'll be regarded as
|
||||||
|
a note-off, and the method will return false
|
||||||
|
|
||||||
|
@see isNoteOff, getNoteNumber, getVelocity, noteOn
|
||||||
|
*/
|
||||||
|
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-down message (using a floating-point velocity).
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@param velocity in the range 0 to 1.0
|
||||||
|
@see isNoteOn
|
||||||
|
*/
|
||||||
|
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-down message (using an integer velocity).
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@param velocity in the range 0 to 127
|
||||||
|
@see isNoteOn
|
||||||
|
*/
|
||||||
|
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this message is a 'key-up' event.
|
||||||
|
|
||||||
|
If returnTrueForNoteOnVelocity0 is true, then his will also return true
|
||||||
|
for a note-on event with a velocity of 0.
|
||||||
|
|
||||||
|
@see isNoteOn, getNoteNumber, getVelocity, noteOff
|
||||||
|
*/
|
||||||
|
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-up message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@param velocity in the range 0 to 1.0
|
||||||
|
@see isNoteOff
|
||||||
|
*/
|
||||||
|
static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-up message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@param velocity in the range 0 to 127
|
||||||
|
@see isNoteOff
|
||||||
|
*/
|
||||||
|
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-up message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@see isNoteOff
|
||||||
|
*/
|
||||||
|
static MidiMessage noteOff (int channel, int noteNumber) noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this message is a 'key-down' or 'key-up' event.
|
||||||
|
|
||||||
|
@see isNoteOn, isNoteOff
|
||||||
|
*/
|
||||||
|
bool isNoteOnOrOff() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the midi note number for note-on and note-off messages.
|
||||||
|
If the message isn't a note-on or off, the value returned is undefined.
|
||||||
|
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber
|
||||||
|
*/
|
||||||
|
int getNoteNumber() const noexcept;
|
||||||
|
|
||||||
|
/** Changes the midi note number of a note-on or note-off message.
|
||||||
|
If the message isn't a note on or off, this will do nothing.
|
||||||
|
*/
|
||||||
|
void setNoteNumber (int newNoteNumber) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the velocity of a note-on or note-off message.
|
||||||
|
|
||||||
|
The value returned will be in the range 0 to 127.
|
||||||
|
If the message isn't a note-on or off event, it will return 0.
|
||||||
|
|
||||||
|
@see getFloatVelocity
|
||||||
|
*/
|
||||||
|
uint8 getVelocity() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the velocity of a note-on or note-off message.
|
||||||
|
|
||||||
|
The value returned will be in the range 0 to 1.0
|
||||||
|
If the message isn't a note-on or off event, it will return 0.
|
||||||
|
|
||||||
|
@see getVelocity, setVelocity
|
||||||
|
*/
|
||||||
|
float getFloatVelocity() const noexcept;
|
||||||
|
|
||||||
|
/** Changes the velocity of a note-on or note-off message.
|
||||||
|
|
||||||
|
If the message isn't a note on or off, this will do nothing.
|
||||||
|
|
||||||
|
@param newVelocity the new velocity, in the range 0 to 1.0
|
||||||
|
@see getFloatVelocity, multiplyVelocity
|
||||||
|
*/
|
||||||
|
void setVelocity (float newVelocity) noexcept;
|
||||||
|
|
||||||
|
/** Multiplies the velocity of a note-on or note-off message by a given amount.
|
||||||
|
|
||||||
|
If the message isn't a note on or off, this will do nothing.
|
||||||
|
|
||||||
|
@param scaleFactor the value by which to multiply the velocity
|
||||||
|
@see setVelocity
|
||||||
|
*/
|
||||||
|
void multiplyVelocity (float scaleFactor) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this message is a 'sustain pedal down' controller message. */
|
||||||
|
bool isSustainPedalOn() const noexcept;
|
||||||
|
/** Returns true if this message is a 'sustain pedal up' controller message. */
|
||||||
|
bool isSustainPedalOff() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this message is a 'sostenuto pedal down' controller message. */
|
||||||
|
bool isSostenutoPedalOn() const noexcept;
|
||||||
|
/** Returns true if this message is a 'sostenuto pedal up' controller message. */
|
||||||
|
bool isSostenutoPedalOff() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this message is a 'soft pedal down' controller message. */
|
||||||
|
bool isSoftPedalOn() const noexcept;
|
||||||
|
/** Returns true if this message is a 'soft pedal up' controller message. */
|
||||||
|
bool isSoftPedalOff() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if the message is a program (patch) change message.
|
||||||
|
@see getProgramChangeNumber, getGMInstrumentName
|
||||||
|
*/
|
||||||
|
bool isProgramChange() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the new program number of a program change message.
|
||||||
|
If the message isn't a program change, the value returned is undefined.
|
||||||
|
@see isProgramChange, getGMInstrumentName
|
||||||
|
*/
|
||||||
|
int getProgramChangeNumber() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a program-change message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param programNumber the midi program number, 0 to 127
|
||||||
|
@see isProgramChange, getGMInstrumentName
|
||||||
|
*/
|
||||||
|
static MidiMessage programChange (int channel, int programNumber) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if the message is a pitch-wheel move.
|
||||||
|
@see getPitchWheelValue, pitchWheel
|
||||||
|
*/
|
||||||
|
bool isPitchWheel() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the pitch wheel position from a pitch-wheel move message.
|
||||||
|
|
||||||
|
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position.
|
||||||
|
If called for messages which aren't pitch wheel events, the number returned will be
|
||||||
|
nonsense.
|
||||||
|
|
||||||
|
@see isPitchWheel
|
||||||
|
*/
|
||||||
|
int getPitchWheelValue() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a pitch-wheel move message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param position the wheel position, in the range 0 to 16383
|
||||||
|
@see isPitchWheel
|
||||||
|
*/
|
||||||
|
static MidiMessage pitchWheel (int channel, int position) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if the message is an aftertouch event.
|
||||||
|
|
||||||
|
For aftertouch events, use the getNoteNumber() method to find out the key
|
||||||
|
that it applies to, and getAftertouchValue() to find out the amount. Use
|
||||||
|
getChannel() to find out the channel.
|
||||||
|
|
||||||
|
@see getAftertouchValue, getNoteNumber
|
||||||
|
*/
|
||||||
|
bool isAftertouch() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the amount of aftertouch from an aftertouch messages.
|
||||||
|
|
||||||
|
The value returned is in the range 0 to 127, and will be nonsense for messages
|
||||||
|
other than aftertouch messages.
|
||||||
|
|
||||||
|
@see isAftertouch
|
||||||
|
*/
|
||||||
|
int getAfterTouchValue() const noexcept;
|
||||||
|
|
||||||
|
/** Creates an aftertouch message.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param noteNumber the key number, 0 to 127
|
||||||
|
@param aftertouchAmount the amount of aftertouch, 0 to 127
|
||||||
|
@see isAftertouch
|
||||||
|
*/
|
||||||
|
static MidiMessage aftertouchChange (int channel,
|
||||||
|
int noteNumber,
|
||||||
|
int aftertouchAmount) noexcept;
|
||||||
|
|
||||||
|
/** Returns true if the message is a channel-pressure change event.
|
||||||
|
|
||||||
|
This is like aftertouch, but common to the whole channel rather than a specific
|
||||||
|
note. Use getChannelPressureValue() to find out the pressure, and getChannel()
|
||||||
|
to find out the channel.
|
||||||
|
|
||||||
|
@see channelPressureChange
|
||||||
|
*/
|
||||||
|
bool isChannelPressure() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the pressure from a channel pressure change message.
|
||||||
|
|
||||||
|
@returns the pressure, in the range 0 to 127
|
||||||
|
@see isChannelPressure, channelPressureChange
|
||||||
|
*/
|
||||||
|
int getChannelPressureValue() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a channel-pressure change event.
|
||||||
|
|
||||||
|
@param channel the midi channel: 1 to 16
|
||||||
|
@param pressure the pressure, 0 to 127
|
||||||
|
@see isChannelPressure
|
||||||
|
*/
|
||||||
|
static MidiMessage channelPressureChange (int channel, int pressure) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a midi controller message.
|
||||||
|
|
||||||
|
@see getControllerNumber, getControllerValue, controllerEvent
|
||||||
|
*/
|
||||||
|
bool isController() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the controller number of a controller message.
|
||||||
|
|
||||||
|
The name of the controller can be looked up using the getControllerName() method.
|
||||||
|
Note that the value returned is invalid for messages that aren't controller changes.
|
||||||
|
|
||||||
|
@see isController, getControllerName, getControllerValue
|
||||||
|
*/
|
||||||
|
int getControllerNumber() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the controller value from a controller message.
|
||||||
|
|
||||||
|
A value 0 to 127 is returned to indicate the new controller position.
|
||||||
|
Note that the value returned is invalid for messages that aren't controller changes.
|
||||||
|
|
||||||
|
@see isController, getControllerNumber
|
||||||
|
*/
|
||||||
|
int getControllerValue() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this message is a controller message and if it has the specified
|
||||||
|
controller type.
|
||||||
|
*/
|
||||||
|
bool isControllerOfType (int controllerType) const noexcept;
|
||||||
|
|
||||||
|
/** Creates a controller message.
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@param controllerType the type of controller
|
||||||
|
@param value the controller value
|
||||||
|
@see isController
|
||||||
|
*/
|
||||||
|
static MidiMessage controllerEvent (int channel,
|
||||||
|
int controllerType,
|
||||||
|
int value) noexcept;
|
||||||
|
|
||||||
|
/** Checks whether this message is an all-notes-off message.
|
||||||
|
@see allNotesOff
|
||||||
|
*/
|
||||||
|
bool isAllNotesOff() const noexcept;
|
||||||
|
|
||||||
|
/** Checks whether this message is an all-sound-off message.
|
||||||
|
@see allSoundOff
|
||||||
|
*/
|
||||||
|
bool isAllSoundOff() const noexcept;
|
||||||
|
|
||||||
|
/** Checks whether this message is a reset all controllers message.
|
||||||
|
@see allControllerOff
|
||||||
|
*/
|
||||||
|
bool isResetAllControllers() const noexcept;
|
||||||
|
|
||||||
|
/** Creates an all-notes-off message.
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@see isAllNotesOff
|
||||||
|
*/
|
||||||
|
static MidiMessage allNotesOff (int channel) noexcept;
|
||||||
|
|
||||||
|
/** Creates an all-sound-off message.
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@see isAllSoundOff
|
||||||
|
*/
|
||||||
|
static MidiMessage allSoundOff (int channel) noexcept;
|
||||||
|
|
||||||
|
/** Creates an all-controllers-off message.
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
*/
|
||||||
|
static MidiMessage allControllersOff (int channel) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this event is a meta-event.
|
||||||
|
|
||||||
|
Meta-events are things like tempo changes, track names, etc.
|
||||||
|
|
||||||
|
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent,
|
||||||
|
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
|
||||||
|
isKeySignatureMetaEvent, isMidiChannelMetaEvent
|
||||||
|
*/
|
||||||
|
bool isMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns a meta-event's type number.
|
||||||
|
|
||||||
|
If the message isn't a meta-event, this will return -1.
|
||||||
|
|
||||||
|
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent,
|
||||||
|
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
|
||||||
|
isKeySignatureMetaEvent, isMidiChannelMetaEvent
|
||||||
|
*/
|
||||||
|
int getMetaEventType() const noexcept;
|
||||||
|
|
||||||
|
/** Returns a pointer to the data in a meta-event.
|
||||||
|
@see isMetaEvent, getMetaEventLength
|
||||||
|
*/
|
||||||
|
const uint8* getMetaEventData() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the length of the data for a meta-event.
|
||||||
|
@see isMetaEvent, getMetaEventData
|
||||||
|
*/
|
||||||
|
int getMetaEventLength() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a 'track' meta-event. */
|
||||||
|
bool isTrackMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is an 'end-of-track' meta-event. */
|
||||||
|
bool isEndOfTrackMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Creates an end-of-track meta-event.
|
||||||
|
@see isEndOfTrackMetaEvent
|
||||||
|
*/
|
||||||
|
static MidiMessage endOfTrack() noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is an 'track name' meta-event.
|
||||||
|
You can use the getTextFromTextMetaEvent() method to get the track's name.
|
||||||
|
*/
|
||||||
|
bool isTrackNameEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is a 'text' meta-event.
|
||||||
|
@see getTextFromTextMetaEvent
|
||||||
|
*/
|
||||||
|
bool isTextMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the text from a text meta-event.
|
||||||
|
@see isTextMetaEvent
|
||||||
|
*/
|
||||||
|
String getTextFromTextMetaEvent() const;
|
||||||
|
|
||||||
|
/** Creates a text meta-event. */
|
||||||
|
static MidiMessage textMetaEvent (int type, StringRef text);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a 'tempo' meta-event.
|
||||||
|
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote
|
||||||
|
*/
|
||||||
|
bool isTempoMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the tick length from a tempo meta-event.
|
||||||
|
|
||||||
|
@param timeFormat the 16-bit time format value from the midi file's header.
|
||||||
|
@returns the tick length (in seconds).
|
||||||
|
@see isTempoMetaEvent
|
||||||
|
*/
|
||||||
|
double getTempoMetaEventTickLength (short timeFormat) const noexcept;
|
||||||
|
|
||||||
|
/** Calculates the seconds-per-quarter-note from a tempo meta-event.
|
||||||
|
@see isTempoMetaEvent, getTempoMetaEventTickLength
|
||||||
|
*/
|
||||||
|
double getTempoSecondsPerQuarterNote() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a tempo meta-event.
|
||||||
|
@see isTempoMetaEvent
|
||||||
|
*/
|
||||||
|
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a 'time-signature' meta-event.
|
||||||
|
@see getTimeSignatureInfo
|
||||||
|
*/
|
||||||
|
bool isTimeSignatureMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the time-signature values from a time-signature meta-event.
|
||||||
|
@see isTimeSignatureMetaEvent
|
||||||
|
*/
|
||||||
|
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept;
|
||||||
|
|
||||||
|
/** Creates a time-signature meta-event.
|
||||||
|
@see isTimeSignatureMetaEvent
|
||||||
|
*/
|
||||||
|
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a 'key-signature' meta-event.
|
||||||
|
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey
|
||||||
|
*/
|
||||||
|
bool isKeySignatureMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the key from a key-signature meta-event.
|
||||||
|
This method must only be called if isKeySignatureMetaEvent() is true.
|
||||||
|
A positive number here indicates the number of sharps in the key signature,
|
||||||
|
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#,
|
||||||
|
-2 = Bb + Eb
|
||||||
|
@see isKeySignatureMetaEvent, isKeySignatureMajorKey
|
||||||
|
*/
|
||||||
|
int getKeySignatureNumberOfSharpsOrFlats() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this key-signature event is major, or false if it's minor.
|
||||||
|
This method must only be called if isKeySignatureMetaEvent() is true.
|
||||||
|
*/
|
||||||
|
bool isKeySignatureMajorKey() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a key-signature meta-event.
|
||||||
|
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps
|
||||||
|
in the key; if negative, the number of flats
|
||||||
|
@param isMinorKey if true, the key is minor; if false, it is major
|
||||||
|
@see isKeySignatureMetaEvent
|
||||||
|
*/
|
||||||
|
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a 'channel' meta-event.
|
||||||
|
|
||||||
|
A channel meta-event specifies the midi channel that should be used
|
||||||
|
for subsequent meta-events.
|
||||||
|
|
||||||
|
@see getMidiChannelMetaEventChannel
|
||||||
|
*/
|
||||||
|
bool isMidiChannelMetaEvent() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the channel number from a channel meta-event.
|
||||||
|
|
||||||
|
@returns the channel, in the range 1 to 16.
|
||||||
|
@see isMidiChannelMetaEvent
|
||||||
|
*/
|
||||||
|
int getMidiChannelMetaEventChannel() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi channel meta-event.
|
||||||
|
|
||||||
|
@param channel the midi channel, in the range 1 to 16
|
||||||
|
@see isMidiChannelMetaEvent
|
||||||
|
*/
|
||||||
|
static MidiMessage midiChannelMetaEvent (int channel) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is an active-sense message. */
|
||||||
|
bool isActiveSense() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a midi start event.
|
||||||
|
@see midiStart
|
||||||
|
*/
|
||||||
|
bool isMidiStart() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi start event. */
|
||||||
|
static MidiMessage midiStart() noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is a midi continue event.
|
||||||
|
@see midiContinue
|
||||||
|
*/
|
||||||
|
bool isMidiContinue() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi continue event. */
|
||||||
|
static MidiMessage midiContinue() noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is a midi stop event.
|
||||||
|
@see midiStop
|
||||||
|
*/
|
||||||
|
bool isMidiStop() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi stop event. */
|
||||||
|
static MidiMessage midiStop() noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is a midi clock event.
|
||||||
|
@see midiClock, songPositionPointer
|
||||||
|
*/
|
||||||
|
bool isMidiClock() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a midi clock event. */
|
||||||
|
static MidiMessage midiClock() noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this is a song-position-pointer message.
|
||||||
|
@see getSongPositionPointerMidiBeat, songPositionPointer
|
||||||
|
*/
|
||||||
|
bool isSongPositionPointer() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the midi beat-number of a song-position-pointer message.
|
||||||
|
@see isSongPositionPointer, songPositionPointer
|
||||||
|
*/
|
||||||
|
int getSongPositionPointerMidiBeat() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a song-position-pointer message.
|
||||||
|
|
||||||
|
The position is a number of midi beats from the start of the song, where 1 midi
|
||||||
|
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there
|
||||||
|
are 4 midi beats in a quarter-note.
|
||||||
|
|
||||||
|
@see isSongPositionPointer, getSongPositionPointerMidiBeat
|
||||||
|
*/
|
||||||
|
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this is a quarter-frame midi timecode message.
|
||||||
|
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue
|
||||||
|
*/
|
||||||
|
bool isQuarterFrame() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the sequence number of a quarter-frame midi timecode message.
|
||||||
|
This will be a value between 0 and 7.
|
||||||
|
@see isQuarterFrame, getQuarterFrameValue, quarterFrame
|
||||||
|
*/
|
||||||
|
int getQuarterFrameSequenceNumber() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the value from a quarter-frame message.
|
||||||
|
This will be the lower nybble of the message's data-byte, a value between 0 and 15
|
||||||
|
*/
|
||||||
|
int getQuarterFrameValue() const noexcept;
|
||||||
|
|
||||||
|
/** Creates a quarter-frame MTC message.
|
||||||
|
|
||||||
|
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte
|
||||||
|
@param value a value 0 to 15 for the lower nybble of the message's data byte
|
||||||
|
*/
|
||||||
|
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept;
|
||||||
|
|
||||||
|
/** SMPTE timecode types.
|
||||||
|
Used by the getFullFrameParameters() and fullFrame() methods.
|
||||||
|
*/
|
||||||
|
enum SmpteTimecodeType
|
||||||
|
{
|
||||||
|
fps24 = 0,
|
||||||
|
fps25 = 1,
|
||||||
|
fps30drop = 2,
|
||||||
|
fps30 = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Returns true if this is a full-frame midi timecode message. */
|
||||||
|
bool isFullFrame() const noexcept;
|
||||||
|
|
||||||
|
/** Extracts the timecode information from a full-frame midi timecode message.
|
||||||
|
|
||||||
|
You should only call this on messages where you've used isFullFrame() to
|
||||||
|
check that they're the right kind.
|
||||||
|
*/
|
||||||
|
void getFullFrameParameters (int& hours,
|
||||||
|
int& minutes,
|
||||||
|
int& seconds,
|
||||||
|
int& frames,
|
||||||
|
SmpteTimecodeType& timecodeType) const noexcept;
|
||||||
|
|
||||||
|
/** Creates a full-frame MTC message. */
|
||||||
|
static MidiMessage fullFrame (int hours,
|
||||||
|
int minutes,
|
||||||
|
int seconds,
|
||||||
|
int frames,
|
||||||
|
SmpteTimecodeType timecodeType);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Types of MMC command.
|
||||||
|
|
||||||
|
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand
|
||||||
|
*/
|
||||||
|
enum MidiMachineControlCommand
|
||||||
|
{
|
||||||
|
mmc_stop = 1,
|
||||||
|
mmc_play = 2,
|
||||||
|
mmc_deferredplay = 3,
|
||||||
|
mmc_fastforward = 4,
|
||||||
|
mmc_rewind = 5,
|
||||||
|
mmc_recordStart = 6,
|
||||||
|
mmc_recordStop = 7,
|
||||||
|
mmc_pause = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Checks whether this is an MMC message.
|
||||||
|
If it is, you can use the getMidiMachineControlCommand() to find out its type.
|
||||||
|
*/
|
||||||
|
bool isMidiMachineControlMessage() const noexcept;
|
||||||
|
|
||||||
|
/** For an MMC message, this returns its type.
|
||||||
|
|
||||||
|
Make sure it's actually an MMC message with isMidiMachineControlMessage() before
|
||||||
|
calling this method.
|
||||||
|
*/
|
||||||
|
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept;
|
||||||
|
|
||||||
|
/** Creates an MMC message. */
|
||||||
|
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command);
|
||||||
|
|
||||||
|
/** Checks whether this is an MMC "goto" message.
|
||||||
|
If it is, the parameters passed-in are set to the time that the message contains.
|
||||||
|
@see midiMachineControlGoto
|
||||||
|
*/
|
||||||
|
bool isMidiMachineControlGoto (int& hours,
|
||||||
|
int& minutes,
|
||||||
|
int& seconds,
|
||||||
|
int& frames) const noexcept;
|
||||||
|
|
||||||
|
/** Creates an MMC "goto" message.
|
||||||
|
This messages tells the device to go to a specific frame.
|
||||||
|
@see isMidiMachineControlGoto
|
||||||
|
*/
|
||||||
|
static MidiMessage midiMachineControlGoto (int hours,
|
||||||
|
int minutes,
|
||||||
|
int seconds,
|
||||||
|
int frames);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a master-volume change message.
|
||||||
|
@param volume the volume, 0 to 1.0
|
||||||
|
*/
|
||||||
|
static MidiMessage masterVolume (float volume);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a system-exclusive message.
|
||||||
|
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
|
||||||
|
*/
|
||||||
|
static MidiMessage createSysExMessage (const void* sysexData,
|
||||||
|
int dataSize);
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Reads a midi variable-length integer.
|
||||||
|
|
||||||
|
@param data the data to read the number from
|
||||||
|
@param numBytesUsed on return, this will be set to the number of bytes that were read
|
||||||
|
*/
|
||||||
|
static int readVariableLengthVal (const uint8* data,
|
||||||
|
int& numBytesUsed) noexcept;
|
||||||
|
|
||||||
|
/** Based on the first byte of a short midi message, this uses a lookup table
|
||||||
|
to return the message length (either 1, 2, or 3 bytes).
|
||||||
|
|
||||||
|
The value passed in must be 0x80 or higher.
|
||||||
|
*/
|
||||||
|
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the name of a midi note number.
|
||||||
|
|
||||||
|
E.g "C", "D#", etc.
|
||||||
|
|
||||||
|
@param noteNumber the midi note number, 0 to 127
|
||||||
|
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise
|
||||||
|
they'll be flattened, e.g. "Db"
|
||||||
|
@param includeOctaveNumber if true, the octave number will be appended to the string,
|
||||||
|
e.g. "C#4"
|
||||||
|
@param octaveNumForMiddleC if an octave number is being appended, this indicates the
|
||||||
|
number that will be used for middle C's octave
|
||||||
|
|
||||||
|
@see getMidiNoteInHertz
|
||||||
|
*/
|
||||||
|
static String getMidiNoteName (int noteNumber,
|
||||||
|
bool useSharps,
|
||||||
|
bool includeOctaveNumber,
|
||||||
|
int octaveNumForMiddleC);
|
||||||
|
|
||||||
|
/** Returns the frequency of a midi note number.
|
||||||
|
|
||||||
|
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch.
|
||||||
|
@see getMidiNoteName
|
||||||
|
*/
|
||||||
|
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept;
|
||||||
|
|
||||||
|
/** Returns true if the given midi note number is a black key. */
|
||||||
|
static bool isMidiNoteBlack (int noteNumber) noexcept;
|
||||||
|
|
||||||
|
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index.
|
||||||
|
|
||||||
|
@param midiInstrumentNumber the program number 0 to 127
|
||||||
|
@see getProgramChangeNumber
|
||||||
|
*/
|
||||||
|
static const char* getGMInstrumentName (int midiInstrumentNumber);
|
||||||
|
|
||||||
|
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number.
|
||||||
|
@param midiBankNumber the bank, 0 to 15
|
||||||
|
*/
|
||||||
|
static const char* getGMInstrumentBankName (int midiBankNumber);
|
||||||
|
|
||||||
|
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number.
|
||||||
|
@param midiNoteNumber the key number, 35 to 81
|
||||||
|
*/
|
||||||
|
static const char* getRhythmInstrumentName (int midiNoteNumber);
|
||||||
|
|
||||||
|
/** Returns the name of a controller type number, or nullptr if unknown for this controller number.
|
||||||
|
@see getControllerNumber
|
||||||
|
*/
|
||||||
|
static const char* getControllerName (int controllerNumber);
|
||||||
|
|
||||||
|
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */
|
||||||
|
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept;
|
||||||
|
|
||||||
|
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */
|
||||||
|
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones,
|
||||||
|
float pitchbendRangeInSemitones) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
#ifndef DOXYGEN
|
||||||
|
union PackedData
|
||||||
|
{
|
||||||
|
uint8* allocatedData;
|
||||||
|
uint8 asBytes[sizeof (uint8*)];
|
||||||
|
};
|
||||||
|
|
||||||
|
PackedData packedData;
|
||||||
|
double timeStamp = 0;
|
||||||
|
int size;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); }
|
||||||
|
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; }
|
||||||
|
uint8* allocateSpace (int);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
405
modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
Normal file
405
modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {}
|
||||||
|
MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (static_cast<MidiMessage&&> (mm)) {}
|
||||||
|
MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiMessageSequence::MidiMessageSequence()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other)
|
||||||
|
{
|
||||||
|
list.addCopiesOf (other.list);
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); ++i)
|
||||||
|
{
|
||||||
|
auto noteOffIndex = other.getIndexOfMatchingKeyUp (i);
|
||||||
|
|
||||||
|
if (noteOffIndex >= 0)
|
||||||
|
list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
|
||||||
|
{
|
||||||
|
MidiMessageSequence otherCopy (other);
|
||||||
|
swapWith (otherCopy);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept
|
||||||
|
: list (static_cast<OwnedArray<MidiEventHolder>&&> (other.list))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept
|
||||||
|
{
|
||||||
|
list = static_cast<OwnedArray<MidiEventHolder>&&> (other.list);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::~MidiMessageSequence()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
|
||||||
|
{
|
||||||
|
list.swapWith (other.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::clear()
|
||||||
|
{
|
||||||
|
list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiMessageSequence::getNumEvents() const noexcept
|
||||||
|
{
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept
|
||||||
|
{
|
||||||
|
return list[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() const noexcept { return list.begin(); }
|
||||||
|
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() const noexcept { return list.end(); }
|
||||||
|
|
||||||
|
double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept
|
||||||
|
{
|
||||||
|
if (auto* meh = list[index])
|
||||||
|
if (auto* noteOff = meh->noteOffObject)
|
||||||
|
return noteOff->message.getTimeStamp();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept
|
||||||
|
{
|
||||||
|
if (auto* meh = list[index])
|
||||||
|
{
|
||||||
|
if (auto* noteOff = meh->noteOffObject)
|
||||||
|
{
|
||||||
|
for (int i = index; i < list.size(); ++i)
|
||||||
|
if (list.getUnchecked(i) == noteOff)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
|
||||||
|
{
|
||||||
|
return list.indexOf (event);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept
|
||||||
|
{
|
||||||
|
auto numEvents = list.size();
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < numEvents; ++i)
|
||||||
|
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp)
|
||||||
|
break;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
double MidiMessageSequence::getStartTime() const noexcept
|
||||||
|
{
|
||||||
|
return getEventTime (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double MidiMessageSequence::getEndTime() const noexcept
|
||||||
|
{
|
||||||
|
return getEventTime (list.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double MidiMessageSequence::getEventTime (const int index) const noexcept
|
||||||
|
{
|
||||||
|
if (auto* meh = list[index])
|
||||||
|
return meh->message.getTimeStamp();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment)
|
||||||
|
{
|
||||||
|
newEvent->message.addToTimeStamp (timeAdjustment);
|
||||||
|
auto time = newEvent->message.getTimeStamp();
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = list.size(); --i >= 0;)
|
||||||
|
if (list.getUnchecked(i)->message.getTimeStamp() <= time)
|
||||||
|
break;
|
||||||
|
|
||||||
|
list.insert (i + 1, newEvent);
|
||||||
|
return newEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment)
|
||||||
|
{
|
||||||
|
return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment)
|
||||||
|
{
|
||||||
|
return addEvent (new MidiEventHolder (static_cast<MidiMessage&&> (newMessage)), timeAdjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp)
|
||||||
|
{
|
||||||
|
if (isPositiveAndBelow (index, list.size()))
|
||||||
|
{
|
||||||
|
if (deleteMatchingNoteUp)
|
||||||
|
deleteEvent (getIndexOfMatchingKeyUp (index), false);
|
||||||
|
|
||||||
|
list.remove (index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment)
|
||||||
|
{
|
||||||
|
for (auto* m : other)
|
||||||
|
{
|
||||||
|
auto newOne = new MidiEventHolder (m->message);
|
||||||
|
newOne->message.addToTimeStamp (timeAdjustment);
|
||||||
|
list.add (newOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
|
||||||
|
double timeAdjustment,
|
||||||
|
double firstAllowableTime,
|
||||||
|
double endOfAllowableDestTimes)
|
||||||
|
{
|
||||||
|
for (auto* m : other)
|
||||||
|
{
|
||||||
|
auto t = m->message.getTimeStamp() + timeAdjustment;
|
||||||
|
|
||||||
|
if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
|
||||||
|
{
|
||||||
|
auto newOne = new MidiEventHolder (m->message);
|
||||||
|
newOne->message.setTimeStamp (t);
|
||||||
|
list.add (newOne);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::sort() noexcept
|
||||||
|
{
|
||||||
|
std::stable_sort (list.begin(), list.end(),
|
||||||
|
[] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::updateMatchedPairs() noexcept
|
||||||
|
{
|
||||||
|
for (int i = 0; i < list.size(); ++i)
|
||||||
|
{
|
||||||
|
auto* meh = list.getUnchecked(i);
|
||||||
|
auto& m1 = meh->message;
|
||||||
|
|
||||||
|
if (m1.isNoteOn())
|
||||||
|
{
|
||||||
|
meh->noteOffObject = nullptr;
|
||||||
|
auto note = m1.getNoteNumber();
|
||||||
|
auto chan = m1.getChannel();
|
||||||
|
auto len = list.size();
|
||||||
|
|
||||||
|
for (int j = i + 1; j < len; ++j)
|
||||||
|
{
|
||||||
|
auto* meh2 = list.getUnchecked(j);
|
||||||
|
auto& m = meh2->message;
|
||||||
|
|
||||||
|
if (m.getNoteNumber() == note && m.getChannel() == chan)
|
||||||
|
{
|
||||||
|
if (m.isNoteOff())
|
||||||
|
{
|
||||||
|
meh->noteOffObject = meh2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.isNoteOn())
|
||||||
|
{
|
||||||
|
auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
|
||||||
|
list.insert (j, newEvent);
|
||||||
|
newEvent->message.setTimeStamp (m.getTimeStamp());
|
||||||
|
meh->noteOffObject = newEvent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::addTimeToMessages (double delta) noexcept
|
||||||
|
{
|
||||||
|
if (delta != 0)
|
||||||
|
for (auto* m : list)
|
||||||
|
m->message.addToTimeStamp (delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
|
||||||
|
MidiMessageSequence& destSequence,
|
||||||
|
const bool alsoIncludeMetaEvents) const
|
||||||
|
{
|
||||||
|
for (auto* meh : list)
|
||||||
|
if (meh->message.isForChannel (channelNumberToExtract)
|
||||||
|
|| (alsoIncludeMetaEvents && meh->message.isMetaEvent()))
|
||||||
|
destSequence.addEvent (meh->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
|
||||||
|
{
|
||||||
|
for (auto* meh : list)
|
||||||
|
if (meh->message.isSysEx())
|
||||||
|
destSequence.addEvent (meh->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove)
|
||||||
|
{
|
||||||
|
for (int i = list.size(); --i >= 0;)
|
||||||
|
if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove))
|
||||||
|
list.remove(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageSequence::deleteSysExMessages()
|
||||||
|
{
|
||||||
|
for (int i = list.size(); --i >= 0;)
|
||||||
|
if (list.getUnchecked(i)->message.isSysEx())
|
||||||
|
list.remove(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array<MidiMessage>& dest)
|
||||||
|
{
|
||||||
|
bool doneProg = false;
|
||||||
|
bool donePitchWheel = false;
|
||||||
|
bool doneControllers[128] = {};
|
||||||
|
|
||||||
|
for (int i = list.size(); --i >= 0;)
|
||||||
|
{
|
||||||
|
auto& mm = list.getUnchecked(i)->message;
|
||||||
|
|
||||||
|
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time)
|
||||||
|
{
|
||||||
|
if (mm.isProgramChange() && ! doneProg)
|
||||||
|
{
|
||||||
|
doneProg = true;
|
||||||
|
dest.add (MidiMessage (mm, 0.0));
|
||||||
|
}
|
||||||
|
else if (mm.isPitchWheel() && ! donePitchWheel)
|
||||||
|
{
|
||||||
|
donePitchWheel = true;
|
||||||
|
dest.add (MidiMessage (mm, 0.0));
|
||||||
|
}
|
||||||
|
else if (mm.isController())
|
||||||
|
{
|
||||||
|
auto controllerNumber = mm.getControllerNumber();
|
||||||
|
jassert (isPositiveAndBelow (controllerNumber, 128));
|
||||||
|
|
||||||
|
if (! doneControllers[controllerNumber])
|
||||||
|
{
|
||||||
|
doneControllers[controllerNumber] = true;
|
||||||
|
dest.add (MidiMessage (mm, 0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
struct MidiMessageSequenceTest : public juce::UnitTest
|
||||||
|
{
|
||||||
|
MidiMessageSequenceTest() : juce::UnitTest ("MidiMessageSequence") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
MidiMessageSequence s;
|
||||||
|
|
||||||
|
s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0));
|
||||||
|
s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0));
|
||||||
|
s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0));
|
||||||
|
s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0));
|
||||||
|
|
||||||
|
beginTest ("Start & end time");
|
||||||
|
expectEquals (s.getStartTime(), 0.0);
|
||||||
|
expectEquals (s.getEndTime(), 8.0);
|
||||||
|
expectEquals (s.getEventTime (1), 2.0);
|
||||||
|
|
||||||
|
beginTest ("Matching note off & ons");
|
||||||
|
s.updateMatchedPairs();
|
||||||
|
expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0);
|
||||||
|
expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0);
|
||||||
|
expectEquals (s.getIndexOfMatchingKeyUp (0), 2);
|
||||||
|
expectEquals (s.getIndexOfMatchingKeyUp (1), 3);
|
||||||
|
|
||||||
|
beginTest ("Time & indeces");
|
||||||
|
expectEquals (s.getNextIndexAtTime (0.5), 1);
|
||||||
|
expectEquals (s.getNextIndexAtTime (2.5), 2);
|
||||||
|
expectEquals (s.getNextIndexAtTime (9.0), 4);
|
||||||
|
|
||||||
|
beginTest ("Deleting events");
|
||||||
|
s.deleteEvent (0, true);
|
||||||
|
expectEquals (s.getNumEvents(), 2);
|
||||||
|
|
||||||
|
beginTest ("Merging sequences");
|
||||||
|
MidiMessageSequence s2;
|
||||||
|
s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0));
|
||||||
|
s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0));
|
||||||
|
s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0));
|
||||||
|
s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0));
|
||||||
|
s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0));
|
||||||
|
s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0));
|
||||||
|
|
||||||
|
s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off
|
||||||
|
s.updateMatchedPairs();
|
||||||
|
|
||||||
|
expectEquals (s.getNumEvents(), 7);
|
||||||
|
expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off
|
||||||
|
expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MidiMessageSequenceTest midiMessageSequenceTests;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace juce
|
300
modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
Normal file
300
modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 sequence of timestamped midi messages.
|
||||||
|
|
||||||
|
This allows the sequence to be manipulated, and also to be read from and
|
||||||
|
written to a standard midi file.
|
||||||
|
|
||||||
|
@see MidiMessage, MidiFile
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiMessageSequence
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates an empty midi sequence object. */
|
||||||
|
MidiMessageSequence();
|
||||||
|
|
||||||
|
/** Creates a copy of another sequence. */
|
||||||
|
MidiMessageSequence (const MidiMessageSequence&);
|
||||||
|
|
||||||
|
/** Replaces this sequence with another one. */
|
||||||
|
MidiMessageSequence& operator= (const MidiMessageSequence&);
|
||||||
|
|
||||||
|
/** Move constructor */
|
||||||
|
MidiMessageSequence (MidiMessageSequence&&) noexcept;
|
||||||
|
|
||||||
|
/** Move assignment operator */
|
||||||
|
MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept;
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiMessageSequence();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Structure used to hold midi events in the sequence.
|
||||||
|
|
||||||
|
These structures act as 'handles' on the events as they are moved about in
|
||||||
|
the list, and make it quick to find the matching note-offs for note-on events.
|
||||||
|
|
||||||
|
@see MidiMessageSequence::getEventPointer
|
||||||
|
*/
|
||||||
|
class MidiEventHolder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiEventHolder();
|
||||||
|
|
||||||
|
/** The message itself, whose timestamp is used to specify the event's time. */
|
||||||
|
MidiMessage message;
|
||||||
|
|
||||||
|
/** The matching note-off event (if this is a note-on event).
|
||||||
|
|
||||||
|
If this isn't a note-on, this pointer will be nullptr.
|
||||||
|
|
||||||
|
Use the MidiMessageSequence::updateMatchedPairs() method to keep these
|
||||||
|
note-offs up-to-date after events have been moved around in the sequence
|
||||||
|
or deleted.
|
||||||
|
*/
|
||||||
|
MidiEventHolder* noteOffObject = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
friend class MidiMessageSequence;
|
||||||
|
MidiEventHolder (const MidiMessage&);
|
||||||
|
MidiEventHolder (MidiMessage&&);
|
||||||
|
JUCE_LEAK_DETECTOR (MidiEventHolder)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Clears the sequence. */
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/** Returns the number of events in the sequence. */
|
||||||
|
int getNumEvents() const noexcept;
|
||||||
|
|
||||||
|
/** Returns a pointer to one of the events. */
|
||||||
|
MidiEventHolder* getEventPointer (int index) const noexcept;
|
||||||
|
|
||||||
|
/** Iterator for the list of MidiEventHolders */
|
||||||
|
MidiEventHolder** begin() const noexcept;
|
||||||
|
|
||||||
|
/** Iterator for the list of MidiEventHolders */
|
||||||
|
MidiEventHolder** 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.
|
||||||
|
@see MidiMessageSequence::MidiEventHolder::noteOffObject
|
||||||
|
*/
|
||||||
|
double getTimeOfMatchingKeyUp (int index) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the index 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 -1.
|
||||||
|
@see MidiMessageSequence::MidiEventHolder::noteOffObject
|
||||||
|
*/
|
||||||
|
int getIndexOfMatchingKeyUp (int index) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the index of an event. */
|
||||||
|
int getIndexOf (const MidiEventHolder* event) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the index of the first event on or after the given timestamp.
|
||||||
|
If the time is beyond the end of the sequence, this will return the
|
||||||
|
number of events.
|
||||||
|
*/
|
||||||
|
int getNextIndexAtTime (double timeStamp) const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the timestamp of the first event in the sequence.
|
||||||
|
@see getEndTime
|
||||||
|
*/
|
||||||
|
double getStartTime() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the timestamp of the last event in the sequence.
|
||||||
|
@see getStartTime
|
||||||
|
*/
|
||||||
|
double getEndTime() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the timestamp of the event at a given index.
|
||||||
|
If the index is out-of-range, this will return 0.0
|
||||||
|
*/
|
||||||
|
double getEventTime (int index) const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Inserts a midi message into the sequence.
|
||||||
|
|
||||||
|
The index at which the new message gets inserted will depend on its timestamp,
|
||||||
|
because the sequence is kept sorted.
|
||||||
|
|
||||||
|
Remember to call updateMatchedPairs() after adding note-on events.
|
||||||
|
|
||||||
|
@param newMessage the new message to add (an internal copy will be made)
|
||||||
|
@param timeAdjustment an optional value to add to the timestamp of the message
|
||||||
|
that will be inserted
|
||||||
|
@see updateMatchedPairs
|
||||||
|
*/
|
||||||
|
MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0);
|
||||||
|
|
||||||
|
/** Inserts a midi message into the sequence.
|
||||||
|
|
||||||
|
The index at which the new message gets inserted will depend on its timestamp,
|
||||||
|
because the sequence is kept sorted.
|
||||||
|
|
||||||
|
Remember to call updateMatchedPairs() after adding note-on events.
|
||||||
|
|
||||||
|
@param newMessage the new message to add (an internal copy will be made)
|
||||||
|
@param timeAdjustment an optional value to add to the timestamp of the message
|
||||||
|
that will be inserted
|
||||||
|
@see updateMatchedPairs
|
||||||
|
*/
|
||||||
|
MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0);
|
||||||
|
|
||||||
|
/** Deletes one of the events in the sequence.
|
||||||
|
|
||||||
|
Remember to call updateMatchedPairs() after removing events.
|
||||||
|
|
||||||
|
@param index the index of the event to delete
|
||||||
|
@param deleteMatchingNoteUp whether to also remove the matching note-off
|
||||||
|
if the event you're removing is a note-on
|
||||||
|
*/
|
||||||
|
void deleteEvent (int index, bool deleteMatchingNoteUp);
|
||||||
|
|
||||||
|
/** Merges another sequence into this one.
|
||||||
|
Remember to call updateMatchedPairs() after using this method.
|
||||||
|
|
||||||
|
@param other the sequence to add from
|
||||||
|
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events
|
||||||
|
as they are read from the other sequence
|
||||||
|
@param firstAllowableDestTime events will not be added if their time is earlier
|
||||||
|
than this time. (This is after their time has been adjusted
|
||||||
|
by the timeAdjustmentDelta)
|
||||||
|
@param endOfAllowableDestTimes events will not be added if their time is equal to
|
||||||
|
or greater than this time. (This is after their time has
|
||||||
|
been adjusted by the timeAdjustmentDelta)
|
||||||
|
*/
|
||||||
|
void addSequence (const MidiMessageSequence& other,
|
||||||
|
double timeAdjustmentDelta,
|
||||||
|
double firstAllowableDestTime,
|
||||||
|
double endOfAllowableDestTimes);
|
||||||
|
|
||||||
|
/** Merges another sequence into this one.
|
||||||
|
Remember to call updateMatchedPairs() after using this method.
|
||||||
|
|
||||||
|
@param other the sequence to add from
|
||||||
|
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events
|
||||||
|
as they are read from the other sequence
|
||||||
|
*/
|
||||||
|
void addSequence (const MidiMessageSequence& other,
|
||||||
|
double timeAdjustmentDelta);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Makes sure all the note-on and note-off pairs are up-to-date.
|
||||||
|
|
||||||
|
Call this after re-ordering messages or deleting/adding messages, and it
|
||||||
|
will scan the list and make sure all the note-offs in the MidiEventHolder
|
||||||
|
structures are pointing at the correct ones.
|
||||||
|
*/
|
||||||
|
void updateMatchedPairs() noexcept;
|
||||||
|
|
||||||
|
/** Forces a sort of the sequence.
|
||||||
|
You may need to call this if you've manually modified the timestamps of some
|
||||||
|
events such that the overall order now needs updating.
|
||||||
|
*/
|
||||||
|
void sort() noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Copies all the messages for a particular midi channel to another sequence.
|
||||||
|
|
||||||
|
@param channelNumberToExtract the midi channel to look for, in the range 1 to 16
|
||||||
|
@param destSequence the sequence that the chosen events should be copied to
|
||||||
|
@param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific
|
||||||
|
channel) will also be copied across.
|
||||||
|
@see extractSysExMessages
|
||||||
|
*/
|
||||||
|
void extractMidiChannelMessages (int channelNumberToExtract,
|
||||||
|
MidiMessageSequence& destSequence,
|
||||||
|
bool alsoIncludeMetaEvents) const;
|
||||||
|
|
||||||
|
/** Copies all midi sys-ex messages to another sequence.
|
||||||
|
@param destSequence this is the sequence to which any sys-exes in this sequence
|
||||||
|
will be added
|
||||||
|
@see extractMidiChannelMessages
|
||||||
|
*/
|
||||||
|
void extractSysExMessages (MidiMessageSequence& destSequence) const;
|
||||||
|
|
||||||
|
/** Removes any messages in this sequence that have a specific midi channel.
|
||||||
|
@param channelNumberToRemove the midi channel to look for, in the range 1 to 16
|
||||||
|
*/
|
||||||
|
void deleteMidiChannelMessages (int channelNumberToRemove);
|
||||||
|
|
||||||
|
/** Removes any sys-ex messages from this sequence. */
|
||||||
|
void deleteSysExMessages();
|
||||||
|
|
||||||
|
/** Adds an offset to the timestamps of all events in the sequence.
|
||||||
|
@param deltaTime the amount to add to each timestamp.
|
||||||
|
*/
|
||||||
|
void addTimeToMessages (double deltaTime) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Scans through the sequence to determine the state of any midi controllers at
|
||||||
|
a given time.
|
||||||
|
|
||||||
|
This will create a sequence of midi controller changes that can be
|
||||||
|
used to set all midi controllers to the state they would be in at the
|
||||||
|
specified time within this sequence.
|
||||||
|
|
||||||
|
As well as controllers, it will also recreate the midi program number
|
||||||
|
and pitch bend position.
|
||||||
|
|
||||||
|
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers
|
||||||
|
for other channels will be ignored.
|
||||||
|
@param time the time at which you want to find out the state - there are
|
||||||
|
no explicit units for this time measurement, it's the same units
|
||||||
|
as used for the timestamps of the messages
|
||||||
|
@param resultMessages an array to which midi controller-change messages will be added. This
|
||||||
|
will be the minimum number of controller changes to recreate the
|
||||||
|
state at the required time.
|
||||||
|
*/
|
||||||
|
void createControllerUpdatesForTime (int channelNumber, double time,
|
||||||
|
Array<MidiMessage>& resultMessages);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Swaps this sequence with another one. */
|
||||||
|
void swapWith (MidiMessageSequence&) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
friend class MidiFile;
|
||||||
|
OwnedArray<MidiEventHolder> list;
|
||||||
|
|
||||||
|
MidiEventHolder* addEvent (MidiEventHolder*, double);
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR (MidiMessageSequence)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
376
modules/juce_audio_basics/midi/juce_MidiRPN.cpp
Normal file
376
modules/juce_audio_basics/midi/juce_MidiRPN.cpp
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MidiRPNDetector::MidiRPNDetector() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiRPNDetector::~MidiRPNDetector() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiRPNDetector::parseControllerMessage (int midiChannel,
|
||||||
|
int controllerNumber,
|
||||||
|
int controllerValue,
|
||||||
|
MidiRPNMessage& result) noexcept
|
||||||
|
{
|
||||||
|
jassert (midiChannel >= 1 && midiChannel <= 16);
|
||||||
|
jassert (controllerNumber >= 0 && controllerNumber < 128);
|
||||||
|
jassert (controllerValue >= 0 && controllerValue < 128);
|
||||||
|
|
||||||
|
return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiRPNDetector::reset() noexcept
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
states[i].parameterMSB = 0xff;
|
||||||
|
states[i].parameterLSB = 0xff;
|
||||||
|
states[i].resetValue();
|
||||||
|
states[i].isNRPN = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiRPNDetector::ChannelState::ChannelState() noexcept
|
||||||
|
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiRPNDetector::ChannelState::handleController (int channel,
|
||||||
|
int controllerNumber,
|
||||||
|
int value,
|
||||||
|
MidiRPNMessage& result) noexcept
|
||||||
|
{
|
||||||
|
switch (controllerNumber)
|
||||||
|
{
|
||||||
|
case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break;
|
||||||
|
case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break;
|
||||||
|
|
||||||
|
case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break;
|
||||||
|
case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break;
|
||||||
|
|
||||||
|
case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result);
|
||||||
|
case 0x26: valueLSB = uint8 (value); break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiRPNDetector::ChannelState::resetValue() noexcept
|
||||||
|
{
|
||||||
|
valueMSB = 0xff;
|
||||||
|
valueLSB = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept
|
||||||
|
{
|
||||||
|
if (parameterMSB < 0x80 && parameterLSB < 0x80)
|
||||||
|
{
|
||||||
|
if (valueMSB < 0x80)
|
||||||
|
{
|
||||||
|
result.channel = channel;
|
||||||
|
result.parameterNumber = (parameterMSB << 7) + parameterLSB;
|
||||||
|
result.isNRPN = isNRPN;
|
||||||
|
|
||||||
|
if (valueLSB < 0x80)
|
||||||
|
{
|
||||||
|
result.value = (valueMSB << 7) + valueLSB;
|
||||||
|
result.is14BitValue = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.value = valueMSB;
|
||||||
|
result.is14BitValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message)
|
||||||
|
{
|
||||||
|
return generate (message.channel,
|
||||||
|
message.parameterNumber,
|
||||||
|
message.value,
|
||||||
|
message.isNRPN,
|
||||||
|
message.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MidiRPNGenerator::generate (int midiChannel,
|
||||||
|
int parameterNumber,
|
||||||
|
int value,
|
||||||
|
bool isNRPN,
|
||||||
|
bool use14BitValue)
|
||||||
|
{
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
jassert (parameterNumber >= 0 && parameterNumber < 16384);
|
||||||
|
jassert (value >= 0 && value < (use14BitValue ? 16384 : 128));
|
||||||
|
|
||||||
|
uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f);
|
||||||
|
uint8 parameterMSB = uint8 (parameterNumber >> 7);
|
||||||
|
|
||||||
|
uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00;
|
||||||
|
uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value);
|
||||||
|
|
||||||
|
uint8 channelByte = uint8 (0xb0 + midiChannel - 1);
|
||||||
|
|
||||||
|
MidiBuffer buffer;
|
||||||
|
|
||||||
|
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0);
|
||||||
|
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0);
|
||||||
|
|
||||||
|
// sending the value LSB is optional, but must come before sending the value MSB:
|
||||||
|
if (use14BitValue)
|
||||||
|
buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0);
|
||||||
|
|
||||||
|
buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class MidiRPNDetectorTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MidiRPNDetectorTests() : UnitTest ("MidiRPNDetector class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("7-bit RPN");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
|
||||||
|
expect (detector.parseControllerMessage (2, 6, 42, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 2);
|
||||||
|
expectEquals (rpn.parameterNumber, 7);
|
||||||
|
expectEquals (rpn.value, 42);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (! rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("14-bit RPN");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (1, 100, 44, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 101, 2, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
|
||||||
|
expect (detector.parseControllerMessage (1, 6, 1, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 1);
|
||||||
|
expectEquals (rpn.parameterNumber, 300);
|
||||||
|
expectEquals (rpn.value, 222);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("RPNs on multiple channels simultaneously");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (1, 100, 44, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 101, 2, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
|
||||||
|
expect (detector.parseControllerMessage (2, 6, 42, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 2);
|
||||||
|
expectEquals (rpn.parameterNumber, 7);
|
||||||
|
expectEquals (rpn.value, 42);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (! rpn.is14BitValue);
|
||||||
|
|
||||||
|
expect (detector.parseControllerMessage (1, 6, 1, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 1);
|
||||||
|
expectEquals (rpn.parameterNumber, 300);
|
||||||
|
expectEquals (rpn.value, 222);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("14-bit RPN with value within 7-bit range");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 101, 0, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 38, 3, rpn));
|
||||||
|
expect (detector.parseControllerMessage (16, 6, 0, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 16);
|
||||||
|
expectEquals (rpn.parameterNumber, 0);
|
||||||
|
expectEquals (rpn.value, 3);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("invalid RPN (wrong order)");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (2, 6, 42, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("14-bit RPN interspersed with unrelated CC messages");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (16, 3, 80, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 4, 81, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 101, 0, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 5, 82, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 5, 83, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 38, 3, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 4, 84, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (16, 3, 85, rpn));
|
||||||
|
expect (detector.parseControllerMessage (16, 6, 0, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 16);
|
||||||
|
expectEquals (rpn.parameterNumber, 0);
|
||||||
|
expectEquals (rpn.value, 3);
|
||||||
|
expect (! rpn.isNRPN);
|
||||||
|
expect (rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("14-bit NRPN");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (1, 98, 44, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 99 , 2, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
|
||||||
|
expect (detector.parseControllerMessage (1, 6, 1, rpn));
|
||||||
|
|
||||||
|
expectEquals (rpn.channel, 1);
|
||||||
|
expectEquals (rpn.parameterNumber, 300);
|
||||||
|
expectEquals (rpn.value, 222);
|
||||||
|
expect (rpn.isNRPN);
|
||||||
|
expect (rpn.is14BitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("reset");
|
||||||
|
{
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
|
||||||
|
detector.reset();
|
||||||
|
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
|
||||||
|
expect (! detector.parseControllerMessage (2, 6, 42, rpn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class MidiRPNGeneratorTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MidiRPNGeneratorTests() : UnitTest ("MidiRPNGenerator class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("generating RPN/NRPN");
|
||||||
|
{
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
|
||||||
|
expectContainsRPN (buffer, 1, 23, 1337, true, true);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
|
||||||
|
expectContainsRPN (buffer, 16, 101, 34, false, false);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MidiRPNMessage message = { 16, 101, 34, false, false };
|
||||||
|
MidiBuffer buffer = MidiRPNGenerator::generate (message);
|
||||||
|
expectContainsRPN (buffer, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
void expectContainsRPN (const MidiBuffer& midiBuffer,
|
||||||
|
int channel,
|
||||||
|
int parameterNumber,
|
||||||
|
int value,
|
||||||
|
bool isNRPN,
|
||||||
|
bool is14BitValue)
|
||||||
|
{
|
||||||
|
MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
|
||||||
|
expectContainsRPN (midiBuffer, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
|
||||||
|
{
|
||||||
|
MidiBuffer::Iterator iter (midiBuffer);
|
||||||
|
MidiMessage midiMessage;
|
||||||
|
MidiRPNMessage result = MidiRPNMessage();
|
||||||
|
MidiRPNDetector detector;
|
||||||
|
int samplePosition; // not actually used, so no need to initialise.
|
||||||
|
|
||||||
|
while (iter.getNextEvent (midiMessage, samplePosition))
|
||||||
|
{
|
||||||
|
if (detector.parseControllerMessage (midiMessage.getChannel(),
|
||||||
|
midiMessage.getControllerNumber(),
|
||||||
|
midiMessage.getControllerValue(),
|
||||||
|
result))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
expectEquals (result.channel, expected.channel);
|
||||||
|
expectEquals (result.parameterNumber, expected.parameterNumber);
|
||||||
|
expectEquals (result.value, expected.value);
|
||||||
|
expect (result.isNRPN == expected.isNRPN);
|
||||||
|
expect (result.is14BitValue == expected.is14BitValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;
|
||||||
|
|
||||||
|
#endif // JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
} // namespace juce
|
154
modules/juce_audio_basics/midi/juce_MidiRPN.h
Normal file
154
modules/juce_audio_basics/midi/juce_MidiRPN.h
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered
|
||||||
|
parameter number) message.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
struct MidiRPNMessage
|
||||||
|
{
|
||||||
|
/** Midi channel of the message, in the range 1 to 16. */
|
||||||
|
int channel;
|
||||||
|
|
||||||
|
/** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */
|
||||||
|
int parameterNumber;
|
||||||
|
|
||||||
|
/** The parameter value, in the range 0 to 16383 (0x3fff).
|
||||||
|
If the message contains no value LSB, the value will be in the range
|
||||||
|
0 to 127 (0x7f).
|
||||||
|
*/
|
||||||
|
int value;
|
||||||
|
|
||||||
|
/** True if this message is an NRPN; false if it is an RPN. */
|
||||||
|
bool isNRPN;
|
||||||
|
|
||||||
|
/** True if the value uses 14-bit resolution (LSB + MSB); false if
|
||||||
|
the value is 7-bit (MSB only).
|
||||||
|
*/
|
||||||
|
bool is14BitValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Parses a stream of MIDI data to assemble RPN and NRPN messages from their
|
||||||
|
constituent MIDI CC messages.
|
||||||
|
|
||||||
|
The detector uses the following parsing rules: the parameter number
|
||||||
|
LSB/MSB can be sent/received in either order and must both come before the
|
||||||
|
parameter value; for the parameter value, LSB always has to be sent/received
|
||||||
|
before the value MSB, otherwise it will be treated as 7-bit (MSB only).
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiRPNDetector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Constructor. */
|
||||||
|
MidiRPNDetector() noexcept;
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiRPNDetector() noexcept;
|
||||||
|
|
||||||
|
/** Resets the RPN detector's internal state, so that it forgets about
|
||||||
|
previously received MIDI CC messages.
|
||||||
|
*/
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Takes the next in a stream of incoming MIDI CC messages and returns true
|
||||||
|
if it forms the last of a sequence that makes an RPN or NPRN.
|
||||||
|
|
||||||
|
If this returns true, then the RPNMessage object supplied will be
|
||||||
|
filled-out with the message's details.
|
||||||
|
(If it returns false then the RPNMessage object will be unchanged).
|
||||||
|
*/
|
||||||
|
bool parseControllerMessage (int midiChannel,
|
||||||
|
int controllerNumber,
|
||||||
|
int controllerValue,
|
||||||
|
MidiRPNMessage& result) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
struct ChannelState
|
||||||
|
{
|
||||||
|
ChannelState() noexcept;
|
||||||
|
bool handleController (int channel, int controllerNumber,
|
||||||
|
int value, MidiRPNMessage&) noexcept;
|
||||||
|
void resetValue() noexcept;
|
||||||
|
bool sendIfReady (int channel, MidiRPNMessage&) noexcept;
|
||||||
|
|
||||||
|
uint8 parameterMSB, parameterLSB, valueMSB, valueLSB;
|
||||||
|
bool isNRPN;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
ChannelState states[16];
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR (MidiRPNDetector)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Generates an appropriate sequence of MIDI CC messages to represent an RPN
|
||||||
|
or NRPN message.
|
||||||
|
|
||||||
|
This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiRPNGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Generates a MIDI sequence representing the given RPN or NRPN message. */
|
||||||
|
static MidiBuffer generate (MidiRPNMessage message);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Generates a MIDI sequence representing an RPN or NRPN message with the
|
||||||
|
given parameters.
|
||||||
|
|
||||||
|
@param channel The MIDI channel of the RPN/NRPN message.
|
||||||
|
|
||||||
|
@param parameterNumber The parameter number, in the range 0 to 16383.
|
||||||
|
|
||||||
|
@param value The parameter value, in the range 0 to 16383, or
|
||||||
|
in the range 0 to 127 if sendAs14BitValue is false.
|
||||||
|
|
||||||
|
@param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default).
|
||||||
|
|
||||||
|
@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).
|
||||||
|
*/
|
||||||
|
static MidiBuffer generate (int channel,
|
||||||
|
int parameterNumber,
|
||||||
|
int value,
|
||||||
|
bool isNRPN = false,
|
||||||
|
bool use14BitValue = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
2212
modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp
Normal file
2212
modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp
Normal file
File diff suppressed because it is too large
Load Diff
398
modules/juce_audio_basics/mpe/juce_MPEInstrument.h
Normal file
398
modules/juce_audio_basics/mpe/juce_MPEInstrument.h
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class represents an instrument handling MPE.
|
||||||
|
|
||||||
|
It has an MPE zone layout and maintans a state of currently
|
||||||
|
active (playing) notes and the values of their dimensions of expression.
|
||||||
|
|
||||||
|
You can trigger and modulate notes:
|
||||||
|
- by passing MIDI messages with the method processNextMidiEvent;
|
||||||
|
- by directly calling the methods noteOn, noteOff etc.
|
||||||
|
|
||||||
|
The class implements the channel and note management logic specified in
|
||||||
|
MPE. If you pass it a message, it will know what notes on what
|
||||||
|
channels (if any) should be affected by that message.
|
||||||
|
|
||||||
|
The class has a Listener class with the three callbacks MPENoteAdded,
|
||||||
|
MPENoteChanged, and MPENoteFinished. Implement such a
|
||||||
|
Listener class to react to note changes and trigger some functionality for
|
||||||
|
your application that depends on the MPE note state.
|
||||||
|
For example, you can use this class to write an MPE visualiser.
|
||||||
|
|
||||||
|
If you want to write a real-time audio synth with MPE functionality,
|
||||||
|
you should instead use the classes MPESynthesiserBase, which adds
|
||||||
|
the ability to render audio and to manage voices.
|
||||||
|
|
||||||
|
@see MPENote, MPEZoneLayout, MPESynthesiser
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPEInstrument
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Constructor.
|
||||||
|
|
||||||
|
This will construct an MPE instrument with inactive lower and upper zones.
|
||||||
|
|
||||||
|
In order to process incoming MIDI, call setZoneLayout, define the layout
|
||||||
|
via MIDI RPN messages, or set the instrument to legacy mode.
|
||||||
|
*/
|
||||||
|
MPEInstrument() noexcept;
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~MPEInstrument();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the current zone layout of the instrument.
|
||||||
|
This happens by value, to enforce thread-safety and class invariants.
|
||||||
|
|
||||||
|
Note: If the instrument is in legacy mode, the return value of this
|
||||||
|
method is unspecified.
|
||||||
|
*/
|
||||||
|
MPEZoneLayout getZoneLayout() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the zone layout of the instrument to the one passed in.
|
||||||
|
As a side effect, this will discard all currently playing notes,
|
||||||
|
and call noteReleased for all of them.
|
||||||
|
|
||||||
|
This will also disable legacy mode in case it was enabled previously.
|
||||||
|
*/
|
||||||
|
void setZoneLayout (MPEZoneLayout newLayout);
|
||||||
|
|
||||||
|
/** Returns true if the given MIDI channel (1-16) is a note channel in 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 isMemberChannel (int midiChannel) noexcept;
|
||||||
|
|
||||||
|
/** Returns true if the given MIDI channel (1-16) is a master channel (channel
|
||||||
|
1 or 16).
|
||||||
|
|
||||||
|
In legacy mode, this will always return false.
|
||||||
|
*/
|
||||||
|
bool isMasterChannel (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
|
||||||
|
notes will be modulated by an incoming MPE message on that channel
|
||||||
|
(pressure, pitchbend, or timbre).
|
||||||
|
|
||||||
|
The default is lastNotePlayedOnChannel.
|
||||||
|
*/
|
||||||
|
enum TrackingMode
|
||||||
|
{
|
||||||
|
lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */
|
||||||
|
lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */
|
||||||
|
highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */
|
||||||
|
allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the pressure dimension. */
|
||||||
|
void setPressureTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the pitchbend dimension. */
|
||||||
|
void setPitchbendTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the timbre dimension. */
|
||||||
|
void setTimbreTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Process a MIDI message and trigger the appropriate method calls
|
||||||
|
(noteOn, noteOff etc.)
|
||||||
|
|
||||||
|
You can override this method if you need some special MIDI message
|
||||||
|
treatment on top of the standard MPE logic implemented here.
|
||||||
|
*/
|
||||||
|
virtual void processNextMidiEvent (const MidiMessage& message);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Request a note-on on the given channel, with the given initial note
|
||||||
|
number and velocity.
|
||||||
|
|
||||||
|
If the message arrives on a valid note channel, this will create a
|
||||||
|
new MPENote and call the noteAdded callback.
|
||||||
|
*/
|
||||||
|
virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity);
|
||||||
|
|
||||||
|
/** Request a note-off.
|
||||||
|
|
||||||
|
If there is a matching playing note, this will release the note
|
||||||
|
(except if it is sustained by a sustain or sostenuto pedal) and call
|
||||||
|
the noteReleased callback.
|
||||||
|
*/
|
||||||
|
virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity);
|
||||||
|
|
||||||
|
/** Request a pitchbend on the given channel with the given value (in units
|
||||||
|
of MIDI pitchwheel position).
|
||||||
|
|
||||||
|
Internally, this will determine whether the pitchwheel move is a
|
||||||
|
per-note pitchbend or a master pitchbend (depending on midiChannel),
|
||||||
|
take the correct per-note or master pitchbend range of the affected MPE
|
||||||
|
zone, and apply the resulting pitchbend to the affected note(s) (if any).
|
||||||
|
*/
|
||||||
|
virtual void pitchbend (int midiChannel, MPEValue pitchbend);
|
||||||
|
|
||||||
|
/** Request a pressure change on the given channel with the given value.
|
||||||
|
|
||||||
|
This will modify the pressure dimension of the note currently held down
|
||||||
|
on this channel (if any). If the channel is a zone master channel,
|
||||||
|
the pressure change will be broadcast to all notes in this zone.
|
||||||
|
*/
|
||||||
|
virtual void pressure (int midiChannel, MPEValue value);
|
||||||
|
|
||||||
|
/** Request a third dimension (timbre) change on the given channel with the
|
||||||
|
given value.
|
||||||
|
|
||||||
|
This will modify the timbre dimension of the note currently held down
|
||||||
|
on this channel (if any). If the channel is a zone master channel,
|
||||||
|
the timbre change will be broadcast to all notes in this zone.
|
||||||
|
*/
|
||||||
|
virtual void timbre (int midiChannel, MPEValue value);
|
||||||
|
|
||||||
|
/** Request a sustain pedal press or release.
|
||||||
|
|
||||||
|
If midiChannel is a zone's master channel, this will act on all notes in
|
||||||
|
that zone; otherwise, nothing will happen.
|
||||||
|
*/
|
||||||
|
virtual void sustainPedal (int midiChannel, bool isDown);
|
||||||
|
|
||||||
|
/** Request a sostenuto pedal press or release.
|
||||||
|
|
||||||
|
If midiChannel is a zone's master channel, this will act on all notes in
|
||||||
|
that zone; otherwise, nothing will happen.
|
||||||
|
*/
|
||||||
|
virtual void sostenutoPedal (int midiChannel, bool isDown);
|
||||||
|
|
||||||
|
/** Discard all currently playing notes.
|
||||||
|
|
||||||
|
This will also call the noteReleased listener callback for all of them.
|
||||||
|
*/
|
||||||
|
void releaseAllNotes();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the number of MPE notes currently played by the instrument. */
|
||||||
|
int getNumPlayingNotes() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the note at the given index.
|
||||||
|
|
||||||
|
If there is no such note, returns an invalid MPENote. The notes are sorted
|
||||||
|
such that the most recently added note is the last element.
|
||||||
|
*/
|
||||||
|
MPENote getNote (int index) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the note currently playing on the given midiChannel with the
|
||||||
|
specified initial MIDI note number, if there is such a note. Otherwise,
|
||||||
|
this returns an invalid MPENote (check with note.isValid() before use!)
|
||||||
|
*/
|
||||||
|
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the most recent note that is playing on the given midiChannel
|
||||||
|
(this will be the note which has received the most recent note-on without
|
||||||
|
a corresponding note-off), if there is such a note. Otherwise, this returns an
|
||||||
|
invalid MPENote (check with note.isValid() before use!)
|
||||||
|
*/
|
||||||
|
MPENote getMostRecentNote (int midiChannel) const noexcept;
|
||||||
|
|
||||||
|
/** Returns the most recent note that is not the note passed in. If there is no
|
||||||
|
such note, this returns an invalid MPENote (check with note.isValid() before use!).
|
||||||
|
|
||||||
|
This helper method might be useful for some custom voice handling algorithms.
|
||||||
|
*/
|
||||||
|
MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Derive from this class to be informed about any changes in the expressive
|
||||||
|
MIDI notes played by this instrument.
|
||||||
|
|
||||||
|
Note: This listener type receives its callbacks immediately, and not
|
||||||
|
via the message thread (so you might be for example in the MIDI thread).
|
||||||
|
Therefore you should never do heavy work such as graphics rendering etc.
|
||||||
|
inside those callbacks.
|
||||||
|
*/
|
||||||
|
class JUCE_API Listener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~Listener() {}
|
||||||
|
|
||||||
|
/** Implement this callback to be informed whenever a new expressive MIDI
|
||||||
|
note is triggered.
|
||||||
|
*/
|
||||||
|
virtual void noteAdded (MPENote newNote) = 0;
|
||||||
|
|
||||||
|
/** Implement this callback to be informed whenever a currently playing
|
||||||
|
MPE note's pressure value changes.
|
||||||
|
*/
|
||||||
|
virtual void notePressureChanged (MPENote changedNote) = 0;
|
||||||
|
|
||||||
|
/** Implement this callback to be informed whenever a currently playing
|
||||||
|
MPE note's pitchbend value changes.
|
||||||
|
|
||||||
|
Note: This can happen if the note itself is bent, if there is a
|
||||||
|
master channel pitchbend event, or if both occur simultaneously.
|
||||||
|
Call MPENote::getFrequencyInHertz to get the effective note frequency.
|
||||||
|
*/
|
||||||
|
virtual void notePitchbendChanged (MPENote changedNote) = 0;
|
||||||
|
|
||||||
|
/** Implement this callback to be informed whenever a currently playing
|
||||||
|
MPE note's timbre value changes.
|
||||||
|
*/
|
||||||
|
virtual void noteTimbreChanged (MPENote changedNote) = 0;
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
called instead.
|
||||||
|
*/
|
||||||
|
virtual void noteKeyStateChanged (MPENote changedNote) = 0;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Adds a listener. */
|
||||||
|
void addListener (Listener* listenerToAdd) noexcept;
|
||||||
|
|
||||||
|
/** Removes a listener. */
|
||||||
|
void removeListener (Listener* listenerToRemove) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Puts the instrument into legacy mode.
|
||||||
|
As a side effect, this will discard all currently playing notes,
|
||||||
|
and call noteReleased for all of them.
|
||||||
|
|
||||||
|
This special zone layout mode is for backwards compatibility with
|
||||||
|
non-MPE MIDI devices. In this mode, the instrument will ignore the
|
||||||
|
current MPE zone layout. It will instead take a range of MIDI channels
|
||||||
|
(default: all channels 1-16) and treat them as note channels, with no
|
||||||
|
master channel. MIDI channels outside of this range will be ignored.
|
||||||
|
|
||||||
|
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
|
||||||
|
Must be between 0 and 96, otherwise behaviour is undefined.
|
||||||
|
The default pitchbend range in legacy mode is +/- 2 semitones.
|
||||||
|
|
||||||
|
@param channelRange The range of MIDI channels to use for notes when in legacy mode.
|
||||||
|
The default is to use all MIDI channels (1-16).
|
||||||
|
|
||||||
|
To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
|
||||||
|
*/
|
||||||
|
void enableLegacyMode (int pitchbendRange = 2,
|
||||||
|
Range<int> channelRange = Range<int> (1, 17));
|
||||||
|
|
||||||
|
/** Returns true if the instrument is in legacy mode, false otherwise. */
|
||||||
|
bool isLegacyModeEnabled() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
Range<int> getLegacyModeChannelRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModeChannelRange (Range<int> channelRange);
|
||||||
|
|
||||||
|
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
int getLegacyModePitchbendRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModePitchbendRange (int pitchbendRange);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
CriticalSection lock;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
Array<MPENote> notes;
|
||||||
|
MPEZoneLayout zoneLayout;
|
||||||
|
ListenerList<Listener> listeners;
|
||||||
|
|
||||||
|
uint8 lastPressureLowerBitReceivedOnChannel[16];
|
||||||
|
uint8 lastTimbreLowerBitReceivedOnChannel[16];
|
||||||
|
bool isMemberChannelSustained[16];
|
||||||
|
|
||||||
|
struct LegacyMode
|
||||||
|
{
|
||||||
|
bool isEnabled;
|
||||||
|
Range<int> channelRange;
|
||||||
|
int pitchbendRange;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MPEDimension
|
||||||
|
{
|
||||||
|
MPEDimension() noexcept : trackingMode (lastNotePlayedOnChannel) {}
|
||||||
|
TrackingMode trackingMode;
|
||||||
|
MPEValue lastValueReceivedOnChannel[16];
|
||||||
|
MPEValue MPENote::* value;
|
||||||
|
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacyMode legacyMode;
|
||||||
|
MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
|
||||||
|
|
||||||
|
void updateDimension (int midiChannel, MPEDimension&, MPEValue);
|
||||||
|
void updateDimensionMaster (bool, MPEDimension&, MPEValue);
|
||||||
|
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue);
|
||||||
|
void callListenersDimensionChanged (const MPENote&, const MPEDimension&);
|
||||||
|
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const;
|
||||||
|
|
||||||
|
void processMidiNoteOnMessage (const MidiMessage&);
|
||||||
|
void processMidiNoteOffMessage (const MidiMessage&);
|
||||||
|
void processMidiPitchWheelMessage (const MidiMessage&);
|
||||||
|
void processMidiChannelPressureMessage (const MidiMessage&);
|
||||||
|
void processMidiControllerMessage (const MidiMessage&);
|
||||||
|
void processMidiResetAllControllersMessage (const MidiMessage&);
|
||||||
|
void handlePressureMSB (int midiChannel, int value) noexcept;
|
||||||
|
void handlePressureLSB (int midiChannel, int value) noexcept;
|
||||||
|
void handleTimbreMSB (int midiChannel, int value) noexcept;
|
||||||
|
void handleTimbreLSB (int midiChannel, int value) noexcept;
|
||||||
|
void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto);
|
||||||
|
|
||||||
|
const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept;
|
||||||
|
MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept;
|
||||||
|
const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept;
|
||||||
|
MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept;
|
||||||
|
const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept;
|
||||||
|
MPENote* getLastNotePlayedPtr (int midiChannel) noexcept;
|
||||||
|
const MPENote* getHighestNotePtr (int midiChannel) const noexcept;
|
||||||
|
MPENote* getHighestNotePtr (int midiChannel) noexcept;
|
||||||
|
const MPENote* getLowestNotePtr (int midiChannel) const noexcept;
|
||||||
|
MPENote* getLowestNotePtr (int midiChannel) noexcept;
|
||||||
|
void updateNoteTotalPitchbend (MPENote&);
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
238
modules/juce_audio_basics/mpe/juce_MPEMessages.cpp
Normal file
238
modules/juce_audio_basics/mpe/juce_MPEMessages.cpp
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange)
|
||||||
|
{
|
||||||
|
auto buffer = MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false);
|
||||||
|
|
||||||
|
buffer.addEvents (setLowerZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0);
|
||||||
|
buffer.addEvents (setLowerZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange)
|
||||||
|
{
|
||||||
|
auto buffer = MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false);
|
||||||
|
|
||||||
|
buffer.addEvents (setUpperZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0);
|
||||||
|
buffer.addEvents (setUpperZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setLowerZonePerNotePitchbendRange (int perNotePitchbendRange)
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (2, 0, perNotePitchbendRange, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setUpperZonePerNotePitchbendRange (int perNotePitchbendRange)
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (15, 0, perNotePitchbendRange, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setLowerZoneMasterPitchbendRange (int masterPitchbendRange)
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (1, 0, masterPitchbendRange, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setUpperZoneMasterPitchbendRange (int masterPitchbendRange)
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (16, 0, masterPitchbendRange, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::clearLowerZone()
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 0, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::clearUpperZone()
|
||||||
|
{
|
||||||
|
return MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, 0, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::clearAllZones()
|
||||||
|
{
|
||||||
|
MidiBuffer buffer;
|
||||||
|
|
||||||
|
buffer.addEvents (clearLowerZone(), 0, -1, 0);
|
||||||
|
buffer.addEvents (clearUpperZone(), 0, -1, 0);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout)
|
||||||
|
{
|
||||||
|
MidiBuffer buffer;
|
||||||
|
|
||||||
|
buffer.addEvents (clearAllZones(), 0, -1, 0);
|
||||||
|
|
||||||
|
auto lowerZone = layout.getLowerZone();
|
||||||
|
if (lowerZone.isActive())
|
||||||
|
buffer.addEvents (setLowerZone (lowerZone.numMemberChannels,
|
||||||
|
lowerZone.perNotePitchbendRange,
|
||||||
|
lowerZone.masterPitchbendRange),
|
||||||
|
0, -1, 0);
|
||||||
|
|
||||||
|
auto upperZone = layout.getUpperZone();
|
||||||
|
if (upperZone.isActive())
|
||||||
|
buffer.addEvents (setUpperZone (upperZone.numMemberChannels,
|
||||||
|
upperZone.perNotePitchbendRange,
|
||||||
|
upperZone.masterPitchbendRange),
|
||||||
|
0, -1, 0);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class MPEMessagesTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MPEMessagesTests() : UnitTest ("MPEMessages class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("add zone");
|
||||||
|
{
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MPEMessages::setLowerZone (7);
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] =
|
||||||
|
{
|
||||||
|
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set up zone
|
||||||
|
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48)
|
||||||
|
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02 // master pbrange (default = 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MPEMessages::setUpperZone (5, 96, 0);
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] =
|
||||||
|
{
|
||||||
|
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x05, // set up zone
|
||||||
|
0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x60, // per-note pbrange (custom)
|
||||||
|
0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // master pbrange (custom)
|
||||||
|
};
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("set per-note pitchbend range");
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MPEMessages::setLowerZonePerNotePitchbendRange (96);
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] = { 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60 };
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
beginTest ("set master pitchbend range");
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MPEMessages::setUpperZoneMasterPitchbendRange (60);
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] = { 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x3c };
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("clear all zones");
|
||||||
|
{
|
||||||
|
MidiBuffer buffer = MPEMessages::clearAllZones();
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone
|
||||||
|
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // clear upper zone
|
||||||
|
};
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("set complete state");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
layout.setLowerZone (7, 96, 0);
|
||||||
|
layout.setUpperZone (7);
|
||||||
|
|
||||||
|
MidiBuffer buffer = MPEMessages::setZoneLayout (layout);
|
||||||
|
|
||||||
|
const uint8 expectedBytes[] = {
|
||||||
|
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone
|
||||||
|
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00, // clear upper zone
|
||||||
|
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set lower zone
|
||||||
|
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60, // per-note pbrange (custom)
|
||||||
|
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // master pbrange (custom)
|
||||||
|
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x07, // set upper zone
|
||||||
|
0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x30, // per-note pbrange (default = 48)
|
||||||
|
0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x02 // master pbrange (default = 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize)
|
||||||
|
{
|
||||||
|
uint8 actualBytes[128] = { 0 };
|
||||||
|
extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes));
|
||||||
|
|
||||||
|
expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes)
|
||||||
|
{
|
||||||
|
std::size_t pos = 0;
|
||||||
|
MidiBuffer::Iterator iter (midiBuffer);
|
||||||
|
MidiMessage midiMessage;
|
||||||
|
int samplePosition; // Note: not actually used, so no need to initialise.
|
||||||
|
|
||||||
|
while (iter.getNextEvent (midiMessage, samplePosition))
|
||||||
|
{
|
||||||
|
const uint8* data = midiMessage.getRawData();
|
||||||
|
std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize();
|
||||||
|
|
||||||
|
if (pos + dataSize > maxBytes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize);
|
||||||
|
pos += dataSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MPEMessagesTests MPEMessagesUnitTests;
|
||||||
|
|
||||||
|
#endif // JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
} // namespace juce
|
116
modules/juce_audio_basics/mpe/juce_MPEMessages.h
Normal file
116
modules/juce_audio_basics/mpe/juce_MPEMessages.h
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This helper class contains the necessary helper functions to generate
|
||||||
|
MIDI messages that are exclusive to MPE, such as defining the upper and lower
|
||||||
|
MPE zones and setting per-note and master pitchbend ranges.
|
||||||
|
You can then send them to your MPE device using MidiOutput::sendBlockOfMessagesNow.
|
||||||
|
|
||||||
|
All other MPE messages like per-note pitchbend, pressure, and third
|
||||||
|
dimension, are ordinary MIDI messages that should be created using the MidiMessage
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
MIDI zone layout of your C++ code and of the MPE device are kept in sync.
|
||||||
|
|
||||||
|
@see MidiMessage, MPEZoneLayout
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPEMessages
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the lower MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setLowerZone (int numMemberChannels = 0,
|
||||||
|
int perNotePitchbendRange = 48,
|
||||||
|
int masterPitchbendRange = 2);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the upper MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setUpperZone (int numMemberChannels = 0,
|
||||||
|
int perNotePitchbendRange = 48,
|
||||||
|
int masterPitchbendRange = 2);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the per-note pitchbend range of the lower MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setLowerZonePerNotePitchbendRange (int perNotePitchbendRange = 48);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the per-note pitchbend range of the upper MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setUpperZonePerNotePitchbendRange (int perNotePitchbendRange = 48);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the master pitchbend range of the lower MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setLowerZoneMasterPitchbendRange (int masterPitchbendRange = 2);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will set the master pitchbend range of the upper MPE zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setUpperZoneMasterPitchbendRange (int masterPitchbendRange = 2);
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will clear the lower zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer clearLowerZone();
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will clear the upper zone.
|
||||||
|
*/
|
||||||
|
static MidiBuffer clearUpperZone();
|
||||||
|
|
||||||
|
/** Returns the sequence of MIDI messages that, if sent to an Expressive
|
||||||
|
MIDI device, will clear the lower and upper zones.
|
||||||
|
*/
|
||||||
|
static MidiBuffer clearAllZones();
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
static MidiBuffer setZoneLayout (MPEZoneLayout layout);
|
||||||
|
|
||||||
|
/** The RPN number used for MPE zone layout messages.
|
||||||
|
|
||||||
|
Pitchbend range messages (both per-note and master) are instead sent
|
||||||
|
on RPN 0 as in standard MIDI 1.0.
|
||||||
|
*/
|
||||||
|
static const int zoneLayoutMessagesRpnNumber = 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
124
modules/juce_audio_basics/mpe/juce_MPENote.cpp
Normal file
124
modules/juce_audio_basics/mpe/juce_MPENote.cpp
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept
|
||||||
|
{
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
|
||||||
|
|
||||||
|
return uint16 ((midiChannel << 7) + midiNoteNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MPENote::MPENote (int midiChannel_,
|
||||||
|
int initialNote_,
|
||||||
|
MPEValue noteOnVelocity_,
|
||||||
|
MPEValue pitchbend_,
|
||||||
|
MPEValue pressure_,
|
||||||
|
MPEValue timbre_,
|
||||||
|
KeyState keyState_) noexcept
|
||||||
|
: noteID (generateNoteID (midiChannel_, initialNote_)),
|
||||||
|
midiChannel (uint8 (midiChannel_)),
|
||||||
|
initialNote (uint8 (initialNote_)),
|
||||||
|
noteOnVelocity (noteOnVelocity_),
|
||||||
|
pitchbend (pitchbend_),
|
||||||
|
pressure (pressure_),
|
||||||
|
initialTimbre (timbre_),
|
||||||
|
timbre (timbre_),
|
||||||
|
keyState (keyState_)
|
||||||
|
{
|
||||||
|
jassert (keyState != MPENote::off);
|
||||||
|
jassert (isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
MPENote::MPENote() noexcept {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MPENote::isValid() const noexcept
|
||||||
|
{
|
||||||
|
return midiChannel > 0 && midiChannel <= 16 && initialNote < 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept
|
||||||
|
{
|
||||||
|
auto pitchInSemitones = double (initialNote) + totalPitchbendInSemitones;
|
||||||
|
return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MPENote::operator== (const MPENote& other) const noexcept
|
||||||
|
{
|
||||||
|
jassert (isValid() && other.isValid());
|
||||||
|
return noteID == other.noteID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPENote::operator!= (const MPENote& other) const noexcept
|
||||||
|
{
|
||||||
|
jassert (isValid() && other.isValid());
|
||||||
|
return noteID != other.noteID;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class MPENoteTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MPENoteTests() : UnitTest ("MPENote class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("getFrequencyInHertz");
|
||||||
|
{
|
||||||
|
MPENote note;
|
||||||
|
note.initialNote = 60;
|
||||||
|
note.totalPitchbendInSemitones = -0.5;
|
||||||
|
expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
void expectEqualsWithinOneCent (double frequencyInHertzActual,
|
||||||
|
double frequencyInHertzExpected)
|
||||||
|
{
|
||||||
|
double ratio = frequencyInHertzActual / frequencyInHertzExpected;
|
||||||
|
double oneCent = 1.0005946;
|
||||||
|
expect (ratio < oneCent);
|
||||||
|
expect (ratio > 1.0 / oneCent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MPENoteTests MPENoteUnitTests;
|
||||||
|
|
||||||
|
#endif // JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
} // namespace juce
|
184
modules/juce_audio_basics/mpe/juce_MPENote.h
Normal file
184
modules/juce_audio_basics/mpe/juce_MPENote.h
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This struct represents a playing MPE note.
|
||||||
|
|
||||||
|
A note is identified by a unique ID, or alternatively, by a MIDI channel
|
||||||
|
and an initial note. It is characterised by five dimensions of continuous
|
||||||
|
expressive control. Their current values are represented as
|
||||||
|
MPEValue objects.
|
||||||
|
|
||||||
|
@see MPEValue
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
struct JUCE_API MPENote
|
||||||
|
{
|
||||||
|
//==============================================================================
|
||||||
|
/** Possible values for the note key state. */
|
||||||
|
enum KeyState
|
||||||
|
{
|
||||||
|
off = 0, /**< The key is up (off). */
|
||||||
|
keyDown = 1, /**< The note key is currently down (pressed). */
|
||||||
|
sustained = 2, /**< The note is sustained (by a sustain or sostenuto pedal). */
|
||||||
|
keyDownAndSustained = 3 /**< The note key is down and sustained (by a sustain or sostenuto pedal). */
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Constructor.
|
||||||
|
|
||||||
|
@param midiChannel The MIDI channel of the note, between 2 and 15.
|
||||||
|
(Channel 1 and channel 16 can never be note channels in MPE).
|
||||||
|
|
||||||
|
@param initialNote The MIDI note number, between 0 and 127.
|
||||||
|
|
||||||
|
@param velocity The note-on velocity of the note.
|
||||||
|
|
||||||
|
@param pitchbend The initial per-note pitchbend of the note.
|
||||||
|
|
||||||
|
@param pressure The initial pressure of the note.
|
||||||
|
|
||||||
|
@param timbre The timbre value of the note.
|
||||||
|
|
||||||
|
@param keyState The key state of the note (whether the key is down
|
||||||
|
and/or the note is sustained). This value must not
|
||||||
|
be MPENote::off, since you are triggering a new note.
|
||||||
|
(If not specified, the default value will be MPENote::keyDown.)
|
||||||
|
*/
|
||||||
|
MPENote (int midiChannel,
|
||||||
|
int initialNote,
|
||||||
|
MPEValue velocity,
|
||||||
|
MPEValue pitchbend,
|
||||||
|
MPEValue pressure,
|
||||||
|
MPEValue timbre,
|
||||||
|
KeyState keyState = MPENote::keyDown) noexcept;
|
||||||
|
|
||||||
|
/** Default constructor.
|
||||||
|
|
||||||
|
Constructs an invalid MPE note (a note with the key state MPENote::off
|
||||||
|
and an invalid MIDI channel. The only allowed use for such a note is to
|
||||||
|
call isValid() on it; everything else is undefined behaviour.
|
||||||
|
*/
|
||||||
|
MPENote() noexcept;
|
||||||
|
|
||||||
|
/** Checks whether the MPE note is valid. */
|
||||||
|
bool isValid() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Invariants that define the note.
|
||||||
|
|
||||||
|
/** A unique ID. Useful to distinguish the note from other simultaneously
|
||||||
|
sounding notes that may use the same note number or MIDI channel.
|
||||||
|
This should never change during the lifetime of a note object.
|
||||||
|
*/
|
||||||
|
uint16 noteID = 0;
|
||||||
|
|
||||||
|
/** The MIDI channel which this note uses.
|
||||||
|
This should never change during the lifetime of an MPENote object.
|
||||||
|
*/
|
||||||
|
uint8 midiChannel = 0;
|
||||||
|
|
||||||
|
/** The MIDI note number that was sent when the note was triggered.
|
||||||
|
This should never change during the lifetime of an MPENote object.
|
||||||
|
*/
|
||||||
|
uint8 initialNote = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// The five dimensions of continuous expressive control
|
||||||
|
|
||||||
|
/** The velocity ("strike") of the note-on.
|
||||||
|
This dimension will stay constant after the note has been turned on.
|
||||||
|
*/
|
||||||
|
MPEValue noteOnVelocity { MPEValue::minValue() };
|
||||||
|
|
||||||
|
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel
|
||||||
|
position). This dimension can be modulated while the note sounds.
|
||||||
|
|
||||||
|
Note: This value is not aware of the currently used pitchbend range,
|
||||||
|
or an additional master pitchbend that may be simultaneously applied.
|
||||||
|
To compute the actual effective pitchbend of an MPENote, you should
|
||||||
|
probably use the member totalPitchbendInSemitones instead.
|
||||||
|
|
||||||
|
@see totalPitchbendInSemitones, getFrequencyInHertz
|
||||||
|
*/
|
||||||
|
MPEValue pitchbend { MPEValue::centreValue() };
|
||||||
|
|
||||||
|
/** Current pressure with which the note is held down.
|
||||||
|
This dimension can be modulated while the note sounds.
|
||||||
|
*/
|
||||||
|
MPEValue pressure { MPEValue::centreValue() };
|
||||||
|
|
||||||
|
/** Inital value of timbre when the note was triggered.
|
||||||
|
This should never change during the lifetime of an MPENote object.
|
||||||
|
*/
|
||||||
|
MPEValue initialTimbre { MPEValue::centreValue() };
|
||||||
|
|
||||||
|
/** Current value of the note's third expressive dimension, typically
|
||||||
|
encoding some kind of timbre parameter.
|
||||||
|
This dimension can be modulated while the note sounds.
|
||||||
|
*/
|
||||||
|
MPEValue timbre { MPEValue::centreValue() };
|
||||||
|
|
||||||
|
/** The release velocity ("lift") of the note after a note-off has been
|
||||||
|
received.
|
||||||
|
This dimension will only have a meaningful value after a note-off has
|
||||||
|
been received for the note (and keyState is set to MPENote::off or
|
||||||
|
MPENote::sustained). Initially, the value is undefined.
|
||||||
|
*/
|
||||||
|
MPEValue noteOffVelocity { MPEValue::minValue() };
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Current effective pitchbend of the note in units of semitones, relative
|
||||||
|
to initialNote. You should use this to compute the actual effective pitch
|
||||||
|
of the note. This value is computed and set by an MPEInstrument to the
|
||||||
|
sum of the per-note pitchbend value (stored in MPEValue::pitchbend)
|
||||||
|
and the master pitchbend of the MPE zone, weighted with the per-note
|
||||||
|
pitchbend range and master pitchbend range of the zone, respectively.
|
||||||
|
|
||||||
|
@see getFrequencyInHertz
|
||||||
|
*/
|
||||||
|
double totalPitchbendInSemitones;
|
||||||
|
|
||||||
|
/** Current key state. Indicates whether the note key is currently down (pressed)
|
||||||
|
and/or the note is sustained (by a sustain or sostenuto pedal).
|
||||||
|
*/
|
||||||
|
KeyState keyState { MPENote::off };
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the current frequency of the note in Hertz. This is the sum of
|
||||||
|
the initialNote and the totalPitchbendInSemitones, converted to Hertz.
|
||||||
|
*/
|
||||||
|
double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if two notes are the same, determined by their unique ID. */
|
||||||
|
bool operator== (const MPENote& other) const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if two notes are different notes, determined by their unique ID. */
|
||||||
|
bool operator!= (const MPENote& other) const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
327
modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp
Normal file
327
modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPESynthesiser::MPESynthesiser()
|
||||||
|
{
|
||||||
|
MPEZoneLayout zoneLayout;
|
||||||
|
zoneLayout.setLowerZone (15);
|
||||||
|
setZoneLayout (zoneLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiser::~MPESynthesiser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart)
|
||||||
|
{
|
||||||
|
jassert (voice != nullptr);
|
||||||
|
voice->currentlyPlayingNote = noteToStart;
|
||||||
|
voice->noteStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff)
|
||||||
|
{
|
||||||
|
jassert (voice != nullptr);
|
||||||
|
voice->currentlyPlayingNote = noteToStop;
|
||||||
|
voice->noteStopped (allowTailOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiser::noteAdded (MPENote newNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
if (auto* voice = findFreeVoice (newNote, shouldStealVoices))
|
||||||
|
startVoice (voice, newNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::notePressureChanged (MPENote changedNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isCurrentlyPlayingNote (changedNote))
|
||||||
|
{
|
||||||
|
voice->currentlyPlayingNote = changedNote;
|
||||||
|
voice->notePressureChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::notePitchbendChanged (MPENote changedNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isCurrentlyPlayingNote (changedNote))
|
||||||
|
{
|
||||||
|
voice->currentlyPlayingNote = changedNote;
|
||||||
|
voice->notePitchbendChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::noteTimbreChanged (MPENote changedNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isCurrentlyPlayingNote (changedNote))
|
||||||
|
{
|
||||||
|
voice->currentlyPlayingNote = changedNote;
|
||||||
|
voice->noteTimbreChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::noteKeyStateChanged (MPENote changedNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isCurrentlyPlayingNote (changedNote))
|
||||||
|
{
|
||||||
|
voice->currentlyPlayingNote = changedNote;
|
||||||
|
voice->noteKeyStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::noteReleased (MPENote finishedNote)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto i = voices.size(); --i >= 0;)
|
||||||
|
{
|
||||||
|
auto* voice = voices.getUnchecked (i);
|
||||||
|
|
||||||
|
if (voice->isCurrentlyPlayingNote(finishedNote))
|
||||||
|
stopVoice (voice, finishedNote, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate)
|
||||||
|
{
|
||||||
|
MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate);
|
||||||
|
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
turnOffAllVoices (false);
|
||||||
|
|
||||||
|
for (auto i = voices.size(); --i >= 0;)
|
||||||
|
voices.getUnchecked (i)->setCurrentSampleRate (newRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::handleMidiEvent (const MidiMessage& m)
|
||||||
|
{
|
||||||
|
if (m.isController())
|
||||||
|
handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue());
|
||||||
|
else if (m.isProgramChange())
|
||||||
|
handleProgramChange (m.getChannel(), m.getProgramChangeNumber());
|
||||||
|
|
||||||
|
MPESynthesiserBase::handleMidiEvent (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (! voice->isActive())
|
||||||
|
return voice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stealIfNoneAvailable)
|
||||||
|
return findVoiceToSteal (noteToFindVoiceFor);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const
|
||||||
|
{
|
||||||
|
// This voice-stealing algorithm applies the following heuristics:
|
||||||
|
// - Re-use the oldest notes first
|
||||||
|
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
|
||||||
|
|
||||||
|
|
||||||
|
// apparently you are trying to render audio without having any voices...
|
||||||
|
jassert (voices.size() > 0);
|
||||||
|
|
||||||
|
// These are the voices we want to protect (ie: only steal if unavoidable)
|
||||||
|
MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
|
||||||
|
MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
|
||||||
|
|
||||||
|
// this is a list of voices we can steal, sorted by how long they've been running
|
||||||
|
Array<MPESynthesiserVoice*> usableVoices;
|
||||||
|
usableVoices.ensureStorageAllocated (voices.size());
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
jassert (voice->isActive()); // We wouldn't be here otherwise
|
||||||
|
|
||||||
|
usableVoices.add (voice);
|
||||||
|
|
||||||
|
// NB: Using a functor rather than a lambda here due to scare-stories about
|
||||||
|
// compilers generating code containing heap allocations..
|
||||||
|
struct Sorter
|
||||||
|
{
|
||||||
|
bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort (usableVoices.begin(), usableVoices.end(), Sorter());
|
||||||
|
|
||||||
|
if (! voice->isPlayingButReleased()) // Don't protect released notes
|
||||||
|
{
|
||||||
|
auto noteNumber = voice->getCurrentlyPlayingNote().initialNote;
|
||||||
|
|
||||||
|
if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote)
|
||||||
|
low = voice;
|
||||||
|
|
||||||
|
if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote)
|
||||||
|
top = voice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
|
||||||
|
if (top == low)
|
||||||
|
top = nullptr;
|
||||||
|
|
||||||
|
// If we want to re-use the voice to trigger a new note,
|
||||||
|
// then The oldest note that's playing the same note number is ideal.
|
||||||
|
if (noteToStealVoiceFor.isValid())
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote)
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that has been released (no finger on it and not held by sustain pedal)
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top && voice->isPlayingButReleased())
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that doesn't have a finger on it:
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top
|
||||||
|
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown
|
||||||
|
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained)
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that isn't protected
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top)
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// We've only got "protected" voices now: lowest note takes priority
|
||||||
|
jassert (low != nullptr);
|
||||||
|
|
||||||
|
// Duophonic synth: give priority to the bass note:
|
||||||
|
if (top != nullptr)
|
||||||
|
return top;
|
||||||
|
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
newVoice->setCurrentSampleRate (getSampleRate());
|
||||||
|
voices.add (newVoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::clearVoices()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
voices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
return voices [index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::removeVoice (const int index)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
voices.remove (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::reduceNumVoices (const int newNumVoices)
|
||||||
|
{
|
||||||
|
// we can't possibly get to a negative number of voices...
|
||||||
|
jassert (newNumVoices >= 0);
|
||||||
|
|
||||||
|
const ScopedLock sl (voicesLock);
|
||||||
|
|
||||||
|
while (voices.size() > newNumVoices)
|
||||||
|
{
|
||||||
|
if (auto* voice = findFreeVoice ({}, true))
|
||||||
|
voices.removeObject (voice);
|
||||||
|
else
|
||||||
|
voices.remove (0); // if there's no voice to steal, kill the oldest voice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// finally make sure the MPE Instrument also doesn't have any notes anymore.
|
||||||
|
instrument->releaseAllNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples)
|
||||||
|
{
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isActive())
|
||||||
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples)
|
||||||
|
{
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isActive())
|
||||||
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
311
modules/juce_audio_basics/mpe/juce_MPESynthesiser.h
Normal file
311
modules/juce_audio_basics/mpe/juce_MPESynthesiser.h
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Base class for an MPE-compatible musical device that can play sounds.
|
||||||
|
|
||||||
|
This class extends MPESynthesiserBase by adding the concept of voices,
|
||||||
|
each of which can play a sound triggered by a MPENote that can be modulated
|
||||||
|
by MPE dimensions like pressure, pitchbend, and timbre, while the note is
|
||||||
|
sounding.
|
||||||
|
|
||||||
|
To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice
|
||||||
|
which can play back one of these sounds at a time.
|
||||||
|
|
||||||
|
Then you can use the addVoice() methods to give the synthesiser a set of voices
|
||||||
|
it can use to play notes. If you only give it one voice it will be monophonic -
|
||||||
|
the more voices it has, the more polyphony it'll have available.
|
||||||
|
|
||||||
|
Then repeatedly call the renderNextBlock() method to produce the audio (inherited
|
||||||
|
from MPESynthesiserBase). The voices will be started, stopped, and modulated
|
||||||
|
automatically, based on the MPE/MIDI messages that the synthesiser receives.
|
||||||
|
|
||||||
|
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it
|
||||||
|
what the target playback rate is. This value is passed on to the voices so that
|
||||||
|
they can pitch their output correctly.
|
||||||
|
|
||||||
|
@see MPESynthesiserBase, MPESynthesiserVoice, MPENote, MPEInstrument
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPESynthesiser : public MPESynthesiserBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Constructor.
|
||||||
|
You'll need to add some voices before it'll make any sound.
|
||||||
|
|
||||||
|
@see addVoice
|
||||||
|
*/
|
||||||
|
MPESynthesiser();
|
||||||
|
|
||||||
|
/** Constructor to pass to the synthesiser a custom MPEInstrument object
|
||||||
|
to handle the MPE note state, MIDI channel assignment etc.
|
||||||
|
(in case you need custom logic for this that goes beyond MIDI and MPE).
|
||||||
|
The synthesiser will take ownership of this object.
|
||||||
|
|
||||||
|
@see MPESynthesiserBase, MPEInstrument
|
||||||
|
*/
|
||||||
|
MPESynthesiser (MPEInstrument* instrument);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MPESynthesiser();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Deletes all voices. */
|
||||||
|
void clearVoices();
|
||||||
|
|
||||||
|
/** Returns the number of voices that have been added. */
|
||||||
|
int getNumVoices() const noexcept { return voices.size(); }
|
||||||
|
|
||||||
|
/** Returns one of the voices that have been added. */
|
||||||
|
MPESynthesiserVoice* getVoice (int index) const;
|
||||||
|
|
||||||
|
/** Adds a new voice to the synth.
|
||||||
|
|
||||||
|
All the voices should be the same class of object and are treated equally.
|
||||||
|
|
||||||
|
The object passed in will be managed by the synthesiser, which will delete
|
||||||
|
it later on when no longer needed. The caller should not retain a pointer to the
|
||||||
|
voice.
|
||||||
|
*/
|
||||||
|
void addVoice (MPESynthesiserVoice* newVoice);
|
||||||
|
|
||||||
|
/** Deletes one of the voices. */
|
||||||
|
void removeVoice (int index);
|
||||||
|
|
||||||
|
/** Reduces the number of voices to newNumVoices.
|
||||||
|
|
||||||
|
This will repeatedly call findVoiceToSteal() and remove that voice, until
|
||||||
|
the total number of voices equals newNumVoices. If newNumVoices is greater than
|
||||||
|
or equal to the current number of voices, this method does nothing.
|
||||||
|
*/
|
||||||
|
void reduceNumVoices (int newNumVoices);
|
||||||
|
|
||||||
|
/** Release all MPE notes and turn off all voices.
|
||||||
|
|
||||||
|
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
|
||||||
|
(if they can do). If this is false, the notes will all be cut off immediately.
|
||||||
|
|
||||||
|
This method is meant to be called by the user, for example to implement
|
||||||
|
a MIDI panic button in a synth.
|
||||||
|
*/
|
||||||
|
virtual void turnOffAllVoices (bool allowTailOff);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** If set to true, then the synth will try to take over an existing voice if
|
||||||
|
it runs out and needs to play another note.
|
||||||
|
|
||||||
|
The value of this boolean is passed into findFreeVoice(), so the result will
|
||||||
|
depend on the implementation of this method.
|
||||||
|
*/
|
||||||
|
void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; }
|
||||||
|
|
||||||
|
/** Returns true if note-stealing is enabled. */
|
||||||
|
bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Tells the synthesiser what the sample rate is for the audio it's being used to render.
|
||||||
|
|
||||||
|
This overrides the implementation in MPESynthesiserBase, to additionally
|
||||||
|
propagate the new value to the voices so that they can use it to render the correct
|
||||||
|
pitches.
|
||||||
|
*/
|
||||||
|
void setCurrentPlaybackSampleRate (double newRate) override;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Handle incoming MIDI events.
|
||||||
|
|
||||||
|
This method will be called automatically according to the MIDI data passed
|
||||||
|
into renderNextBlock(), but you can also call it yourself to manually
|
||||||
|
inject MIDI events.
|
||||||
|
|
||||||
|
This implementation forwards program change messages and non-MPE-related
|
||||||
|
controller messages to handleProgramChange and handleController, respectively,
|
||||||
|
and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal
|
||||||
|
with MPE-related MIDI messages used for MPE notes, zones etc.
|
||||||
|
|
||||||
|
This method can be overridden further if you need to do custom MIDI
|
||||||
|
handling on top of what is provided here.
|
||||||
|
*/
|
||||||
|
void handleMidiEvent (const MidiMessage&) override;
|
||||||
|
|
||||||
|
/** Callback for MIDI controller messages. The default implementation
|
||||||
|
provided here does nothing; override this method if you need custom
|
||||||
|
MIDI controller handling on top of MPE.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock().
|
||||||
|
*/
|
||||||
|
virtual void handleController (int /*midiChannel*/,
|
||||||
|
int /*controllerNumber*/,
|
||||||
|
int /*controllerValue*/) {}
|
||||||
|
|
||||||
|
/** Callback for MIDI program change messages. The default implementation
|
||||||
|
provided here does nothing; override this method if you need to handle
|
||||||
|
those messages.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock().
|
||||||
|
*/
|
||||||
|
virtual void handleProgramChange (int /*midiChannel*/,
|
||||||
|
int /*programNumber*/) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** Attempts to start playing a new note.
|
||||||
|
|
||||||
|
The default method here will find a free voice that is appropriate for
|
||||||
|
playing the given MPENote, and use that voice to start playing the sound.
|
||||||
|
If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled),
|
||||||
|
the synthesiser will use the voice stealing algorithm to find a free voice for
|
||||||
|
the note (if no voices are free otherwise).
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state
|
||||||
|
will become inconsistent.
|
||||||
|
*/
|
||||||
|
virtual void noteAdded (MPENote newNote) override;
|
||||||
|
|
||||||
|
/** Stops playing a note.
|
||||||
|
|
||||||
|
This will be called 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.
|
||||||
|
|
||||||
|
This will find any voice that is currently playing finishedNote,
|
||||||
|
turn its currently playing note off, and call its noteStopped callback.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state
|
||||||
|
will become inconsistent.
|
||||||
|
*/
|
||||||
|
virtual void noteReleased (MPENote finishedNote) override;
|
||||||
|
|
||||||
|
/** Will find any voice that is currently playing changedNote, update its
|
||||||
|
currently playing note, and call its notePressureChanged method.
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Will find any voice that is currently playing changedNote, update its
|
||||||
|
currently playing note, and call its notePitchbendChanged method.
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Will find any voice that is currently playing changedNote, update its
|
||||||
|
currently playing note, and call its noteTimbreChanged method.
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Will find any voice that is currently playing changedNote, update its
|
||||||
|
currently playing note, and call its noteKeyStateChanged method.
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** 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<float>& 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)
|
||||||
|
Override this method if you need to do more work to render your audio.
|
||||||
|
*/
|
||||||
|
virtual void renderNextSubBlock (AudioBuffer<double>& outputAudio,
|
||||||
|
int startSample,
|
||||||
|
int numSamples) override;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Searches through the voices to find one that's not currently playing, and
|
||||||
|
which can play the given MPE note.
|
||||||
|
|
||||||
|
If all voices are active and stealIfNoneAvailable is false, this returns
|
||||||
|
a nullptr. If all voices are active and stealIfNoneAvailable is true,
|
||||||
|
this will call findVoiceToSteal() to find a voice.
|
||||||
|
|
||||||
|
If you need to find a free voice for something else than playing a note
|
||||||
|
(e.g. for deleting it), you can pass an invalid (default-constructed) MPENote.
|
||||||
|
*/
|
||||||
|
virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor,
|
||||||
|
bool stealIfNoneAvailable) const;
|
||||||
|
|
||||||
|
/** Chooses a voice that is most suitable for being re-used to play a new
|
||||||
|
note, or for being deleted by reduceNumVoices.
|
||||||
|
|
||||||
|
The default method will attempt to find the oldest voice that isn't the
|
||||||
|
bottom or top note being played. If that's not suitable for your synth,
|
||||||
|
you can override this method and do something more cunning instead.
|
||||||
|
|
||||||
|
If you pass a valid MPENote for the optional argument, then the note number
|
||||||
|
of that note will be taken into account for finding the ideal voice to steal.
|
||||||
|
If you pass an invalid (default-constructed) MPENote instead, this part of
|
||||||
|
the algorithm will be ignored.
|
||||||
|
*/
|
||||||
|
virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const;
|
||||||
|
|
||||||
|
/** Starts a specified voice and tells it to play a particular MPENote.
|
||||||
|
You should never need to call this, it's called internally by
|
||||||
|
MPESynthesiserBase::instrument via the noteStarted callback,
|
||||||
|
but is protected in case it's useful for some custom subclasses.
|
||||||
|
*/
|
||||||
|
void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart);
|
||||||
|
|
||||||
|
/** Stops a given voice and tells it to stop playing a particular MPENote
|
||||||
|
(which should be the same note it is actually playing).
|
||||||
|
You should never need to call this, it's called internally by
|
||||||
|
MPESynthesiserBase::instrument via the noteReleased callback,
|
||||||
|
but is protected in case it's useful for some custom subclasses.
|
||||||
|
*/
|
||||||
|
void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
OwnedArray<MPESynthesiserVoice> voices;
|
||||||
|
CriticalSection voicesLock;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
bool shouldStealVoices = false;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
180
modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp
Normal file
180
modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPESynthesiserBase::MPESynthesiserBase()
|
||||||
|
: instrument (new MPEInstrument)
|
||||||
|
{
|
||||||
|
instrument->addListener (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst)
|
||||||
|
: instrument (inst)
|
||||||
|
{
|
||||||
|
jassert (instrument != nullptr);
|
||||||
|
instrument->addListener (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->getZoneLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout)
|
||||||
|
{
|
||||||
|
instrument->setZoneLayout (newLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
|
||||||
|
{
|
||||||
|
instrument->enableLegacyMode (pitchbendRange, channelRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->isLegacyModeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->getLegacyModeChannelRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange)
|
||||||
|
{
|
||||||
|
instrument->setLegacyModeChannelRange (channelRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->getLegacyModePitchbendRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange)
|
||||||
|
{
|
||||||
|
instrument->setLegacyModePitchbendRange (pitchbendRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse)
|
||||||
|
{
|
||||||
|
instrument->setPressureTrackingMode (modeToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse)
|
||||||
|
{
|
||||||
|
instrument->setPitchbendTrackingMode (modeToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse)
|
||||||
|
{
|
||||||
|
instrument->setTimbreTrackingMode (modeToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m)
|
||||||
|
{
|
||||||
|
instrument->processNextMidiEvent (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
template <typename floatType>
|
||||||
|
void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio,
|
||||||
|
const MidiBuffer& inputMidi,
|
||||||
|
int startSample,
|
||||||
|
int numSamples)
|
||||||
|
{
|
||||||
|
// you must set the sample rate before using this!
|
||||||
|
jassert (sampleRate != 0);
|
||||||
|
|
||||||
|
MidiBuffer::Iterator midiIterator (inputMidi);
|
||||||
|
midiIterator.setNextSamplePosition (startSample);
|
||||||
|
|
||||||
|
bool firstEvent = true;
|
||||||
|
int midiEventPos;
|
||||||
|
MidiMessage m;
|
||||||
|
|
||||||
|
const ScopedLock sl (noteStateLock);
|
||||||
|
|
||||||
|
while (numSamples > 0)
|
||||||
|
{
|
||||||
|
if (! midiIterator.getNextEvent (m, midiEventPos))
|
||||||
|
{
|
||||||
|
renderNextSubBlock (outputAudio, startSample, numSamples);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto samplesToNextMidiMessage = midiEventPos - startSample;
|
||||||
|
|
||||||
|
if (samplesToNextMidiMessage >= numSamples)
|
||||||
|
{
|
||||||
|
renderNextSubBlock (outputAudio, startSample, numSamples);
|
||||||
|
handleMidiEvent (m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize))
|
||||||
|
{
|
||||||
|
handleMidiEvent (m);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEvent = false;
|
||||||
|
|
||||||
|
renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage);
|
||||||
|
handleMidiEvent (m);
|
||||||
|
startSample += samplesToNextMidiMessage;
|
||||||
|
numSamples -= samplesToNextMidiMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (midiIterator.getNextEvent (m, midiEventPos))
|
||||||
|
handleMidiEvent (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicit instantiation for supported float types:
|
||||||
|
template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
|
||||||
|
template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate)
|
||||||
|
{
|
||||||
|
if (sampleRate != newRate)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (noteStateLock);
|
||||||
|
instrument->releaseAllNotes();
|
||||||
|
sampleRate = newRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept
|
||||||
|
{
|
||||||
|
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
|
||||||
|
minimumSubBlockSize = numSamples;
|
||||||
|
subBlockSubdivisionIsStrict = shouldBeStrict;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
210
modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h
Normal file
210
modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Derive from this class to create a basic audio generator capable of MPE.
|
||||||
|
Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged
|
||||||
|
etc.) to let your audio generator know that MPE notes were triggered, modulated,
|
||||||
|
or released. What to do inside them, and how that influences your audio generator,
|
||||||
|
is up to you!
|
||||||
|
|
||||||
|
This class uses an instance of MPEInstrument internally to handle the MPE
|
||||||
|
note state logic.
|
||||||
|
|
||||||
|
This class is a very low-level base class for an MPE instrument. If you need
|
||||||
|
something more sophisticated, have a look at MPESynthesiser. This class extends
|
||||||
|
MPESynthesiserBase by adding the concept of voices that can play notes,
|
||||||
|
a voice stealing algorithm, and much more.
|
||||||
|
|
||||||
|
@see MPESynthesiser, MPEInstrument
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Constructor. */
|
||||||
|
MPESynthesiserBase();
|
||||||
|
|
||||||
|
/** Constructor.
|
||||||
|
|
||||||
|
If you use this constructor, the synthesiser will take ownership of the
|
||||||
|
provided instrument object, and will use it internally to handle the
|
||||||
|
MPE note state logic.
|
||||||
|
This is useful if you want to use an instance of your own class derived
|
||||||
|
from MPEInstrument for the MPE logic.
|
||||||
|
*/
|
||||||
|
MPESynthesiserBase (MPEInstrument* instrument);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the synthesiser's internal MPE zone layout.
|
||||||
|
This happens by value, to enforce thread-safety and class invariants.
|
||||||
|
*/
|
||||||
|
MPEZoneLayout getZoneLayout() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in.
|
||||||
|
As a side effect, this will discard all currently playing notes,
|
||||||
|
call noteReleased for all of them, and disable legacy mode (if previously enabled).
|
||||||
|
*/
|
||||||
|
void setZoneLayout (MPEZoneLayout newLayout);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Tells the synthesiser what the sample rate is for the audio it's being
|
||||||
|
used to render.
|
||||||
|
*/
|
||||||
|
virtual void setCurrentPlaybackSampleRate (double sampleRate);
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
double getSampleRate() const noexcept { return sampleRate; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates the next block of audio output.
|
||||||
|
|
||||||
|
Call this to make sound. This will chop up the AudioBuffer into subBlock
|
||||||
|
pieces separated by events in the MIDI buffer, and then call
|
||||||
|
processNextSubBlock on each one of them. In between you will get calls
|
||||||
|
to noteAdded/Changed/Finished, where you can update parameters that
|
||||||
|
depend on those notes to use for your audio rendering.
|
||||||
|
*/
|
||||||
|
template <typename floatType>
|
||||||
|
void renderNextBlock (AudioBuffer<floatType>& outputAudio,
|
||||||
|
const MidiBuffer& inputMidi,
|
||||||
|
int startSample,
|
||||||
|
int numSamples);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Handle incoming MIDI events (called from renderNextBlock).
|
||||||
|
|
||||||
|
The default implementation provided here simply forwards everything
|
||||||
|
to MPEInstrument::processNextMidiEvent, where it is used to update the
|
||||||
|
MPE notes, zones etc. MIDI messages not relevant for MPE are ignored.
|
||||||
|
|
||||||
|
This method can be overridden if you need to do custom MIDI handling
|
||||||
|
on top of MPE. The MPESynthesiser class overrides this to implement
|
||||||
|
callbacks for MIDI program changes and non-MPE-related MIDI controller
|
||||||
|
messages.
|
||||||
|
*/
|
||||||
|
virtual void handleMidiEvent (const MidiMessage&);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.
|
||||||
|
|
||||||
|
When rendering, the audio blocks that are passed into renderNextBlock() will be split up
|
||||||
|
into smaller blocks that lie between all the incoming midi messages, and it is these smaller
|
||||||
|
sub-blocks that are rendered with multiple calls to renderVoices().
|
||||||
|
|
||||||
|
Obviously in a pathological case where there are midi messages on every sample, then
|
||||||
|
renderVoices() could be called once per sample and lead to poor performance, so this
|
||||||
|
setting allows you to set a lower limit on the block size.
|
||||||
|
|
||||||
|
The default setting is 32, which means that midi messages are accurate to about < 1ms
|
||||||
|
accuracy, which is probably fine for most purposes, but you may want to increase or
|
||||||
|
decrease this value for your synth.
|
||||||
|
|
||||||
|
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples.
|
||||||
|
|
||||||
|
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed
|
||||||
|
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate
|
||||||
|
(this can sometimes help to avoid quantisation or phasing issues).
|
||||||
|
*/
|
||||||
|
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Puts the synthesiser into legacy mode.
|
||||||
|
|
||||||
|
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
|
||||||
|
Must be between 0 and 96, otherwise behaviour is undefined.
|
||||||
|
The default pitchbend range in legacy mode is +/- 2 semitones.
|
||||||
|
@param channelRange The range of MIDI channels to use for notes when in legacy mode.
|
||||||
|
The default is to use all MIDI channels (1-16).
|
||||||
|
|
||||||
|
To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
|
||||||
|
*/
|
||||||
|
void enableLegacyMode (int pitchbendRange = 2,
|
||||||
|
Range<int> channelRange = Range<int> (1, 17));
|
||||||
|
|
||||||
|
/** Returns true if the instrument is in legacy mode, false otherwise. */
|
||||||
|
bool isLegacyModeEnabled() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
Range<int> getLegacyModeChannelRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModeChannelRange (Range<int> channelRange);
|
||||||
|
|
||||||
|
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
int getLegacyModePitchbendRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModePitchbendRange (int pitchbendRange);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
using TrackingMode = MPEInstrument::TrackingMode;
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the pressure dimension. */
|
||||||
|
void setPressureTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the pitchbend dimension. */
|
||||||
|
void setPitchbendTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
/** Set the MPE tracking mode for the timbre dimension. */
|
||||||
|
void setTimbreTrackingMode (TrackingMode modeToUse);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** Implement this method to render your audio inside.
|
||||||
|
@see renderNextBlock
|
||||||
|
*/
|
||||||
|
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio,
|
||||||
|
int startSample,
|
||||||
|
int numSamples) = 0;
|
||||||
|
|
||||||
|
/** Implement this method if you want to render 64-bit audio as well;
|
||||||
|
otherwise leave blank.
|
||||||
|
*/
|
||||||
|
virtual void renderNextSubBlock (AudioBuffer<double>& /*outputAudio*/,
|
||||||
|
int /*startSample*/,
|
||||||
|
int /*numSamples*/) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** @internal */
|
||||||
|
std::unique_ptr<MPEInstrument> instrument;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
CriticalSection noteStateLock;
|
||||||
|
double sampleRate = 0.0;
|
||||||
|
int minimumSubBlockSize = 32;
|
||||||
|
bool subBlockSubdivisionIsStrict = false;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
55
modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp
Normal file
55
modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPESynthesiserVoice::MPESynthesiserVoice()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MPESynthesiserVoice::~MPESynthesiserVoice()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept
|
||||||
|
{
|
||||||
|
return isActive() && currentlyPlayingNote.noteID == note.noteID;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
190
modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h
Normal file
190
modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Represents an MPE voice that an MPESynthesiser can use to play a sound.
|
||||||
|
|
||||||
|
A voice plays a single sound at a time, and a synthesiser holds an array of
|
||||||
|
voices so that it can play polyphonically.
|
||||||
|
|
||||||
|
@see MPESynthesiser, MPENote
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPESynthesiserVoice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Constructor. */
|
||||||
|
MPESynthesiserVoice();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~MPESynthesiserVoice();
|
||||||
|
|
||||||
|
/** Returns the MPENote that this voice is currently playing.
|
||||||
|
Returns an invalid MPENote if no note is playing
|
||||||
|
(you can check this using MPENote::isValid() or MPEVoice::isActive()).
|
||||||
|
*/
|
||||||
|
MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; }
|
||||||
|
|
||||||
|
/** Returns true if the voice is currently playing the given MPENote
|
||||||
|
(as identified by the note's initial note number and MIDI channel).
|
||||||
|
*/
|
||||||
|
bool isCurrentlyPlayingNote (MPENote note) const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if this voice is currently busy playing a sound.
|
||||||
|
By default this just checks whether getCurrentlyPlayingNote()
|
||||||
|
returns a valid MPE note, but can be overridden for more advanced checking.
|
||||||
|
*/
|
||||||
|
virtual bool isActive() const { return currentlyPlayingNote.isValid(); }
|
||||||
|
|
||||||
|
/** Returns true if a voice is sounding in its release phase. **/
|
||||||
|
bool isPlayingButReleased() const noexcept;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that a new note has started on it.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void noteStarted() = 0;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
|
||||||
|
If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all
|
||||||
|
sound immediately, and must call clearCurrentNote() to reset the state of this voice
|
||||||
|
and allow the synth to reassign it another sound.
|
||||||
|
|
||||||
|
If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to
|
||||||
|
begin fading out its sound, and it can stop playing until it's finished. As soon as it
|
||||||
|
finishes playing (during the rendering callback), it must make sure that it calls
|
||||||
|
clearCurrentNote().
|
||||||
|
*/
|
||||||
|
virtual void noteStopped (bool allowTailOff) = 0;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that its currently playing note
|
||||||
|
has changed its pressure value.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void notePressureChanged() = 0;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that its currently playing note
|
||||||
|
has changed its pitchbend value.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
|
||||||
|
Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency
|
||||||
|
of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend.
|
||||||
|
*/
|
||||||
|
virtual void notePitchbendChanged() = 0;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that its currently playing note
|
||||||
|
has changed its timbre value.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void noteTimbreChanged() = 0;
|
||||||
|
|
||||||
|
/** Called by the MPESynthesiser to let the voice know that its currently playing note
|
||||||
|
has changed its key state.
|
||||||
|
This typically happens when a sustain or sostenuto pedal is pressed or released (on
|
||||||
|
an MPE channel relevant for this note), or if the note key is lifted while the sustained
|
||||||
|
or sostenuto pedal is still held down.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void noteKeyStateChanged() = 0;
|
||||||
|
|
||||||
|
/** Renders the next block of data for this voice.
|
||||||
|
|
||||||
|
The output audio data must be added to the current contents of the buffer provided.
|
||||||
|
Only the region of the buffer between startSample and (startSample + numSamples)
|
||||||
|
should be altered by this method.
|
||||||
|
|
||||||
|
If the voice is currently silent, it should just return without doing anything.
|
||||||
|
|
||||||
|
If the sound that the voice is playing finishes during the course of this rendered
|
||||||
|
block, it must call clearCurrentNote(), to tell the synthesiser that it has finished.
|
||||||
|
|
||||||
|
The size of the blocks that are rendered can change each time it is called, and may
|
||||||
|
involve rendering as little as 1 sample at a time. In between rendering callbacks,
|
||||||
|
the voice's methods will be called to tell it about note and controller events.
|
||||||
|
*/
|
||||||
|
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
|
||||||
|
int startSample,
|
||||||
|
int numSamples) = 0;
|
||||||
|
|
||||||
|
/** Renders the next block of 64-bit data for this voice.
|
||||||
|
|
||||||
|
Support for 64-bit audio is optional. You can choose to not override this method if
|
||||||
|
you don't need it (the default implementation simply does nothing).
|
||||||
|
*/
|
||||||
|
virtual void renderNextBlock (AudioBuffer<double>& /*outputBuffer*/,
|
||||||
|
int /*startSample*/,
|
||||||
|
int /*numSamples*/) {}
|
||||||
|
|
||||||
|
/** Changes the voice's reference sample rate.
|
||||||
|
|
||||||
|
The rate is set so that subclasses know the output rate and can set their pitch
|
||||||
|
accordingly.
|
||||||
|
|
||||||
|
This method is called by the synth, and subclasses can access the current rate with
|
||||||
|
the currentSampleRate member.
|
||||||
|
*/
|
||||||
|
virtual void setCurrentSampleRate (double newRate) { currentSampleRate = newRate; }
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** Resets the state of this voice after a sound has finished playing.
|
||||||
|
|
||||||
|
The subclass must call this when it finishes playing a note and becomes available
|
||||||
|
to play new ones.
|
||||||
|
|
||||||
|
It must either call it in the stopNote() method, or if the voice is tailing off,
|
||||||
|
then it should call it later during the renderNextBlock method, as soon as it
|
||||||
|
finishes its tail-off.
|
||||||
|
|
||||||
|
It can also be called at any time during the render callback if the sound happens
|
||||||
|
to have finished, e.g. if it's playing a sample and the sample finishes.
|
||||||
|
*/
|
||||||
|
void clearCurrentNote() noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
double currentSampleRate = 0.0;
|
||||||
|
MPENote currentlyPlayingNote;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
friend class MPESynthesiser;
|
||||||
|
uint32 noteStartTime = 0;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
478
modules/juce_audio_basics/mpe/juce_MPEUtils.cpp
Normal file
478
modules/juce_audio_basics/mpe/juce_MPEUtils.cpp
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse)
|
||||||
|
: zone (new MPEZoneLayout::Zone (zoneToUse)),
|
||||||
|
channelIncrement (zone->isLowerZone() ? 1 : -1),
|
||||||
|
numChannels (zone->numMemberChannels),
|
||||||
|
firstChannel (zone->getFirstMemberChannel()),
|
||||||
|
lastChannel (zone->getLastMemberChannel()),
|
||||||
|
midiChannelLastAssigned (firstChannel - channelIncrement)
|
||||||
|
{
|
||||||
|
// must be an active MPE zone!
|
||||||
|
jassert (numChannels > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPEChannelAssigner::MPEChannelAssigner (Range<int> channelRange)
|
||||||
|
: isLegacy (true),
|
||||||
|
channelIncrement (1),
|
||||||
|
numChannels (channelRange.getLength()),
|
||||||
|
firstChannel (channelRange.getStart()),
|
||||||
|
lastChannel (channelRange.getEnd() - 1),
|
||||||
|
midiChannelLastAssigned (firstChannel - channelIncrement)
|
||||||
|
{
|
||||||
|
// must have at least one channel!
|
||||||
|
jassert (! channelRange.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
|
||||||
|
{
|
||||||
|
if (numChannels == 1)
|
||||||
|
return firstChannel;
|
||||||
|
|
||||||
|
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||||
|
{
|
||||||
|
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
|
||||||
|
{
|
||||||
|
midiChannelLastAssigned = ch;
|
||||||
|
midiChannels[ch].notes.add (noteNumber);
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
|
||||||
|
{
|
||||||
|
if (ch == lastChannel + channelIncrement) // loop wrap-around
|
||||||
|
ch = firstChannel;
|
||||||
|
|
||||||
|
if (midiChannels[ch].isFree())
|
||||||
|
{
|
||||||
|
midiChannelLastAssigned = ch;
|
||||||
|
midiChannels[ch].notes.add (noteNumber);
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == midiChannelLastAssigned)
|
||||||
|
break; // no free channels!
|
||||||
|
}
|
||||||
|
|
||||||
|
midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
|
||||||
|
midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
|
||||||
|
|
||||||
|
return midiChannelLastAssigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelAssigner::noteOff (int noteNumber)
|
||||||
|
{
|
||||||
|
for (auto& ch : midiChannels)
|
||||||
|
{
|
||||||
|
if (ch.notes.removeAllInstancesOf (noteNumber) > 0)
|
||||||
|
{
|
||||||
|
ch.lastNotePlayed = noteNumber;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelAssigner::allNotesOff()
|
||||||
|
{
|
||||||
|
for (auto& ch : midiChannels)
|
||||||
|
{
|
||||||
|
if (ch.notes.size() > 0)
|
||||||
|
ch.lastNotePlayed = ch.notes.getLast();
|
||||||
|
|
||||||
|
ch.notes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
|
||||||
|
{
|
||||||
|
auto channelWithClosestNote = firstChannel;
|
||||||
|
int closestNoteDistance = 127;
|
||||||
|
|
||||||
|
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||||
|
{
|
||||||
|
for (auto note : midiChannels[ch].notes)
|
||||||
|
{
|
||||||
|
auto noteDistance = std::abs (note - noteNumber);
|
||||||
|
|
||||||
|
if (noteDistance > 0 && noteDistance < closestNoteDistance)
|
||||||
|
{
|
||||||
|
closestNoteDistance = noteDistance;
|
||||||
|
channelWithClosestNote = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelWithClosestNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap)
|
||||||
|
: zone (zoneToRemap),
|
||||||
|
channelIncrement (zone.isLowerZone() ? 1 : -1),
|
||||||
|
firstChannel (zone.getFirstMemberChannel()),
|
||||||
|
lastChannel (zone.getLastMemberChannel())
|
||||||
|
{
|
||||||
|
// must be an active MPE zone!
|
||||||
|
jassert (zone.numMemberChannels > 0);
|
||||||
|
zeroArrays();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept
|
||||||
|
{
|
||||||
|
auto channel = message.getChannel();
|
||||||
|
|
||||||
|
if (! zone.isUsingChannelAsMemberChannel (channel))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
|
||||||
|
{
|
||||||
|
clearSource (mpeSourceID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
|
||||||
|
|
||||||
|
if (messageIsNoteData (message))
|
||||||
|
{
|
||||||
|
++counter;
|
||||||
|
|
||||||
|
// fast path - no remap
|
||||||
|
if (applyRemapIfExisting (channel, sourceAndChannelID, message))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// find existing remap
|
||||||
|
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||||
|
if (applyRemapIfExisting (chan, sourceAndChannelID, message))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// no remap necessary
|
||||||
|
if (sourceAndChannel[channel] == notMPE)
|
||||||
|
{
|
||||||
|
lastUsed[channel] = counter;
|
||||||
|
sourceAndChannel[channel] = sourceAndChannelID;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remap source & channel to new channel
|
||||||
|
auto chan = getBestChanToReuse();
|
||||||
|
|
||||||
|
sourceAndChannel[chan] = sourceAndChannelID;
|
||||||
|
lastUsed[chan] = counter;
|
||||||
|
message.setChannel (chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelRemapper::reset() noexcept
|
||||||
|
{
|
||||||
|
for (auto& s : sourceAndChannel)
|
||||||
|
s = notMPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelRemapper::clearChannel (int channel) noexcept
|
||||||
|
{
|
||||||
|
sourceAndChannel[channel] = notMPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelRemapper::clearSource (uint32 mpeSourceID)
|
||||||
|
{
|
||||||
|
for (auto& s : sourceAndChannel)
|
||||||
|
{
|
||||||
|
if (uint32 (s >> 5) == mpeSourceID)
|
||||||
|
{
|
||||||
|
s = notMPE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
|
||||||
|
{
|
||||||
|
if (sourceAndChannel[channel] == sourceAndChannelID)
|
||||||
|
{
|
||||||
|
if (m.isNoteOff())
|
||||||
|
sourceAndChannel[channel] = notMPE;
|
||||||
|
else
|
||||||
|
lastUsed[channel] = counter;
|
||||||
|
|
||||||
|
m.setChannel (channel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPEChannelRemapper::getBestChanToReuse() const noexcept
|
||||||
|
{
|
||||||
|
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||||
|
if (sourceAndChannel[chan] == notMPE)
|
||||||
|
return chan;
|
||||||
|
|
||||||
|
auto bestChan = firstChannel;
|
||||||
|
auto bestLastUse = counter;
|
||||||
|
|
||||||
|
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||||
|
{
|
||||||
|
if (lastUsed[chan] < bestLastUse)
|
||||||
|
{
|
||||||
|
bestLastUse = lastUsed[chan];
|
||||||
|
bestChan = chan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestChan;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEChannelRemapper::zeroArrays()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 17; ++i)
|
||||||
|
{
|
||||||
|
sourceAndChannel[i] = 0;
|
||||||
|
lastUsed[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
struct MPEUtilsUnitTests : public UnitTest
|
||||||
|
{
|
||||||
|
MPEUtilsUnitTests()
|
||||||
|
: UnitTest ("MPE Utilities", "MIDI/MPE")
|
||||||
|
{}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("MPEChannelAssigner");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
// lower
|
||||||
|
{
|
||||||
|
layout.setLowerZone (15);
|
||||||
|
|
||||||
|
// lower zone
|
||||||
|
MPEChannelAssigner channelAssigner (layout.getLowerZone());
|
||||||
|
|
||||||
|
// check that channels are assigned in correct order
|
||||||
|
int noteNum = 60;
|
||||||
|
for (int ch = 2; ch <= 16; ++ch)
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||||
|
|
||||||
|
// check that note-offs are processed
|
||||||
|
channelAssigner.noteOff (60);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
|
||||||
|
|
||||||
|
channelAssigner.noteOff (61);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
|
||||||
|
|
||||||
|
// check that assigned channel was last to play note
|
||||||
|
channelAssigner.noteOff (65);
|
||||||
|
channelAssigner.noteOff (66);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||||
|
|
||||||
|
// find closest channel playing nonequal note
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||||
|
|
||||||
|
// all notes off
|
||||||
|
channelAssigner.allNotesOff();
|
||||||
|
|
||||||
|
// last note played
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||||
|
|
||||||
|
// normal assignment
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// upper
|
||||||
|
{
|
||||||
|
layout.setUpperZone (15);
|
||||||
|
|
||||||
|
// upper zone
|
||||||
|
MPEChannelAssigner channelAssigner (layout.getUpperZone());
|
||||||
|
|
||||||
|
// check that channels are assigned in correct order
|
||||||
|
int noteNum = 60;
|
||||||
|
for (int ch = 15; ch >= 1; --ch)
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||||
|
|
||||||
|
// check that note-offs are processed
|
||||||
|
channelAssigner.noteOff (60);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
|
||||||
|
|
||||||
|
channelAssigner.noteOff (61);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
|
||||||
|
|
||||||
|
// check that assigned channel was last to play note
|
||||||
|
channelAssigner.noteOff (65);
|
||||||
|
channelAssigner.noteOff (66);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||||
|
|
||||||
|
// find closest channel playing nonequal note
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||||
|
|
||||||
|
// all notes off
|
||||||
|
channelAssigner.allNotesOff();
|
||||||
|
|
||||||
|
// last note played
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||||
|
|
||||||
|
// normal assignment
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
{
|
||||||
|
MPEChannelAssigner channelAssigner;
|
||||||
|
|
||||||
|
// check that channels are assigned in correct order
|
||||||
|
int noteNum = 60;
|
||||||
|
for (int ch = 1; ch <= 16; ++ch)
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||||
|
|
||||||
|
// check that note-offs are processed
|
||||||
|
channelAssigner.noteOff (60);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
|
||||||
|
|
||||||
|
channelAssigner.noteOff (61);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
|
||||||
|
|
||||||
|
// check that assigned channel was last to play note
|
||||||
|
channelAssigner.noteOff (65);
|
||||||
|
channelAssigner.noteOff (66);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||||
|
|
||||||
|
// find closest channel playing nonequal note
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||||
|
|
||||||
|
// all notes off
|
||||||
|
channelAssigner.allNotesOff();
|
||||||
|
|
||||||
|
// last note played
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||||
|
|
||||||
|
// normal assignment
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
|
||||||
|
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("MPEChannelRemapper");
|
||||||
|
{
|
||||||
|
// 3 different MPE 'sources', constant IDs
|
||||||
|
const int sourceID1 = 0;
|
||||||
|
const int sourceID2 = 1;
|
||||||
|
const int sourceID3 = 2;
|
||||||
|
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
{
|
||||||
|
layout.setLowerZone (15);
|
||||||
|
|
||||||
|
// lower zone
|
||||||
|
MPEChannelRemapper channelRemapper (layout.getLowerZone());
|
||||||
|
|
||||||
|
// first source, shouldn't remap
|
||||||
|
for (int ch = 2; ch <= 16; ++ch)
|
||||||
|
{
|
||||||
|
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
|
||||||
|
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
|
||||||
|
expectEquals (noteOn.getChannel(), ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
|
||||||
|
|
||||||
|
// remap onto oldest last-used channel
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
|
||||||
|
expectEquals (noteOn.getChannel(), 2);
|
||||||
|
|
||||||
|
// remap onto oldest last-used channel
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
|
||||||
|
expectEquals (noteOn.getChannel(), 3);
|
||||||
|
|
||||||
|
// remap to correct channel for source ID
|
||||||
|
auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
|
||||||
|
expectEquals (noteOff.getChannel(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
layout.setUpperZone (15);
|
||||||
|
|
||||||
|
// upper zone
|
||||||
|
MPEChannelRemapper channelRemapper (layout.getUpperZone());
|
||||||
|
|
||||||
|
// first source, shouldn't remap
|
||||||
|
for (int ch = 15; ch >= 1; --ch)
|
||||||
|
{
|
||||||
|
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
|
||||||
|
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
|
||||||
|
expectEquals (noteOn.getChannel(), ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
|
||||||
|
|
||||||
|
// remap onto oldest last-used channel
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
|
||||||
|
expectEquals (noteOn.getChannel(), 15);
|
||||||
|
|
||||||
|
// remap onto oldest last-used channel
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
|
||||||
|
expectEquals (noteOn.getChannel(), 14);
|
||||||
|
|
||||||
|
// remap to correct channel for source ID
|
||||||
|
auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
|
||||||
|
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
|
||||||
|
expectEquals (noteOff.getChannel(), 14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MPEUtilsUnitTests MPEUtilsUnitTests;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
} // namespace juce
|
150
modules/juce_audio_basics/mpe/juce_MPEUtils.h
Normal file
150
modules/juce_audio_basics/mpe/juce_MPEUtils.h
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class handles the assignment of new MIDI notes to member channels of an active
|
||||||
|
MPE zone.
|
||||||
|
|
||||||
|
To use it, create an instance passing in the MPE zone that it should operate on
|
||||||
|
and then call use the findMidiChannelForNewNote() method for all note-on messages
|
||||||
|
and the noteOff() method for all note-off messages.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class MPEChannelAssigner
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Constructor.
|
||||||
|
|
||||||
|
This will assign channels within the range of the specified MPE zone.
|
||||||
|
*/
|
||||||
|
MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse);
|
||||||
|
|
||||||
|
/** Legacy mode constructor.
|
||||||
|
|
||||||
|
This will assign channels within the specified range.
|
||||||
|
*/
|
||||||
|
MPEChannelAssigner (Range<int> channelRange = Range<int> (1, 17));
|
||||||
|
|
||||||
|
/** This method will use a set of rules recommended in the MPE specification to
|
||||||
|
determine which member channel the specified MIDI note should be assigned to
|
||||||
|
and will return this channel number.
|
||||||
|
|
||||||
|
The rules have the following precedence:
|
||||||
|
- find a free channel on which the last note played was the same as the one specified
|
||||||
|
- find the next free channel in round-robin assignment
|
||||||
|
- find the channel number that is currently playing the closest note (but not the same)
|
||||||
|
|
||||||
|
@param noteNumber the MIDI note number to be assigned to a channel
|
||||||
|
@returns the zone's member channel that this note should be assigned to
|
||||||
|
*/
|
||||||
|
int findMidiChannelForNewNote (int noteNumber) noexcept;
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
void noteOff (int noteNumber);
|
||||||
|
|
||||||
|
/** Call this to clear all currently playing notes. */
|
||||||
|
void allNotesOff();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isLegacy = false;
|
||||||
|
std::unique_ptr<MPEZoneLayout::Zone> zone;
|
||||||
|
int channelIncrement, numChannels, firstChannel, lastChannel, midiChannelLastAssigned;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
struct MidiChannel
|
||||||
|
{
|
||||||
|
Array<int> notes;
|
||||||
|
int lastNotePlayed = -1;
|
||||||
|
bool isFree() const noexcept { return notes.isEmpty(); }
|
||||||
|
};
|
||||||
|
MidiChannel midiChannels[17];
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class handles the logic for remapping MIDI note messages from multiple MPE
|
||||||
|
sources onto a specified MPE zone.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class MPEChannelRemapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Used to indicate that a particular source & channel combination is not currently using MPE. */
|
||||||
|
static const uint32 notMPE = 0;
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Remaps the MIDI channel of the specified MIDI message (if necessary).
|
||||||
|
|
||||||
|
Note that the MidiMessage object passed in will have it's channel changed if it
|
||||||
|
needs to be remapped.
|
||||||
|
|
||||||
|
@param message the message to be remapped
|
||||||
|
@param mpeSourceID the ID of the MPE source of the message. This is up to the
|
||||||
|
user to define and keep constant
|
||||||
|
*/
|
||||||
|
void remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Resets all the source & channel combinations. */
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
/** Clears a specified channel of this MPE zone. */
|
||||||
|
void clearChannel (int channel) noexcept;
|
||||||
|
|
||||||
|
/** Clears all channels in use by a specified source. */
|
||||||
|
void clearSource (uint32 mpeSourceID);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MPEZoneLayout::Zone zone;
|
||||||
|
|
||||||
|
int channelIncrement;
|
||||||
|
int firstChannel, lastChannel;
|
||||||
|
|
||||||
|
uint32 sourceAndChannel[17];
|
||||||
|
uint32 lastUsed[17];
|
||||||
|
uint32 counter = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept;
|
||||||
|
int getBestChanToReuse() const noexcept;
|
||||||
|
|
||||||
|
void zeroArrays();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool messageIsNoteData (const MidiMessage& m) { return (*m.getRawData() & 0xf0) != 0xf0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
170
modules/juce_audio_basics/mpe/juce_MPEValue.cpp
Normal file
170
modules/juce_audio_basics/mpe/juce_MPEValue.cpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPEValue::MPEValue() noexcept {}
|
||||||
|
MPEValue::MPEValue (int value) : normalisedValue (value) {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MPEValue MPEValue::from7BitInt (int value) noexcept
|
||||||
|
{
|
||||||
|
jassert (value >= 0 && value <= 127);
|
||||||
|
|
||||||
|
auto valueAs14Bit = value <= 64 ? value << 7
|
||||||
|
: int (jmap<float> (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192;
|
||||||
|
|
||||||
|
return { valueAs14Bit };
|
||||||
|
}
|
||||||
|
|
||||||
|
MPEValue MPEValue::from14BitInt (int value) noexcept
|
||||||
|
{
|
||||||
|
jassert (value >= 0 && value <= 16383);
|
||||||
|
return { value };
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); }
|
||||||
|
MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); }
|
||||||
|
MPEValue MPEValue::maxValue() noexcept { return MPEValue::from7BitInt (127); }
|
||||||
|
|
||||||
|
int MPEValue::as7BitInt() const noexcept
|
||||||
|
{
|
||||||
|
return normalisedValue >> 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPEValue::as14BitInt() const noexcept
|
||||||
|
{
|
||||||
|
return normalisedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
float MPEValue::asSignedFloat() const noexcept
|
||||||
|
{
|
||||||
|
return (normalisedValue < 8192)
|
||||||
|
? jmap<float> (float (normalisedValue), 0.0f, 8192.0f, -1.0f, 0.0f)
|
||||||
|
: jmap<float> (float (normalisedValue), 8192.0f, 16383.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float MPEValue::asUnsignedFloat() const noexcept
|
||||||
|
{
|
||||||
|
return jmap<float> (float (normalisedValue), 0.0f, 16383.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
bool MPEValue::operator== (const MPEValue& other) const noexcept
|
||||||
|
{
|
||||||
|
return normalisedValue == other.normalisedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPEValue::operator!= (const MPEValue& other) const noexcept
|
||||||
|
{
|
||||||
|
return ! operator== (other);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class MPEValueTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MPEValueTests() : UnitTest ("MPEValue class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("comparison operator");
|
||||||
|
{
|
||||||
|
MPEValue value1 = MPEValue::from7BitInt (7);
|
||||||
|
MPEValue value2 = MPEValue::from7BitInt (7);
|
||||||
|
MPEValue value3 = MPEValue::from7BitInt (8);
|
||||||
|
|
||||||
|
expect (value1 == value1);
|
||||||
|
expect (value1 == value2);
|
||||||
|
expect (value1 != value3);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("special values");
|
||||||
|
{
|
||||||
|
expectEquals (MPEValue::minValue().as7BitInt(), 0);
|
||||||
|
expectEquals (MPEValue::minValue().as14BitInt(), 0);
|
||||||
|
|
||||||
|
expectEquals (MPEValue::centreValue().as7BitInt(), 64);
|
||||||
|
expectEquals (MPEValue::centreValue().as14BitInt(), 8192);
|
||||||
|
|
||||||
|
expectEquals (MPEValue::maxValue().as7BitInt(), 127);
|
||||||
|
expectEquals (MPEValue::maxValue().as14BitInt(), 16383);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("zero/minimum value");
|
||||||
|
{
|
||||||
|
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||||
|
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("maximum value");
|
||||||
|
{
|
||||||
|
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f);
|
||||||
|
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("centre value");
|
||||||
|
{
|
||||||
|
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f);
|
||||||
|
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("value halfway between min and centre");
|
||||||
|
{
|
||||||
|
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f);
|
||||||
|
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
void expectValuesConsistent (MPEValue value,
|
||||||
|
int expectedValueAs7BitInt,
|
||||||
|
int expectedValueAs14BitInt,
|
||||||
|
float expectedValueAsSignedFloat,
|
||||||
|
float expectedValueAsUnsignedFloat)
|
||||||
|
{
|
||||||
|
expectEquals (value.as7BitInt(), expectedValueAs7BitInt);
|
||||||
|
expectEquals (value.as14BitInt(), expectedValueAs14BitInt);
|
||||||
|
expectFloatWithinRelativeError (value.asSignedFloat(), expectedValueAsSignedFloat, 0.0001f);
|
||||||
|
expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError)
|
||||||
|
{
|
||||||
|
const float maxAbsoluteError = jmax (1.0f, std::abs (expectedValue)) * maxRelativeError;
|
||||||
|
expect (std::abs (expectedValue - actualValue) < maxAbsoluteError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MPEValueTests MPEValueUnitTests;
|
||||||
|
|
||||||
|
#endif // JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
} // namespace juce
|
97
modules/juce_audio_basics/mpe/juce_MPEValue.h
Normal file
97
modules/juce_audio_basics/mpe/juce_MPEValue.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class represents a single value for any of the MPE
|
||||||
|
dimensions of control. It supports values with 7-bit or 14-bit resolutions
|
||||||
|
(corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper
|
||||||
|
functions to query the value in a variety of representations that can be
|
||||||
|
useful in an audio or MIDI context.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPEValue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Default constructor.
|
||||||
|
|
||||||
|
Constructs an MPEValue corresponding to the centre value.
|
||||||
|
*/
|
||||||
|
MPEValue() noexcept;
|
||||||
|
|
||||||
|
/** Constructs an MPEValue from an integer between 0 and 127
|
||||||
|
(using 7-bit precision).
|
||||||
|
*/
|
||||||
|
static MPEValue from7BitInt (int value) noexcept;
|
||||||
|
|
||||||
|
/** Constructs an MPEValue from an integer between 0 and 16383
|
||||||
|
(using 14-bit precision).
|
||||||
|
*/
|
||||||
|
static MPEValue from14BitInt (int value) noexcept;
|
||||||
|
|
||||||
|
/** Constructs an MPEValue corresponding to the centre value. */
|
||||||
|
static MPEValue centreValue() noexcept;
|
||||||
|
|
||||||
|
/** Constructs an MPEValue corresponding to the minimum value. */
|
||||||
|
static MPEValue minValue() noexcept;
|
||||||
|
|
||||||
|
/** Constructs an MPEValue corresponding to the maximum value. */
|
||||||
|
static MPEValue maxValue() noexcept;
|
||||||
|
|
||||||
|
/** Retrieves the current value as an integer between 0 and 127.
|
||||||
|
|
||||||
|
Information will be lost if the value was initialised with a precision
|
||||||
|
higher than 7-bit.
|
||||||
|
*/
|
||||||
|
int as7BitInt() const noexcept;
|
||||||
|
|
||||||
|
/** Retrieves the current value as an integer between 0 and 16383.
|
||||||
|
|
||||||
|
Resolution will be lost if the value was initialised with a precision
|
||||||
|
higher than 14-bit.
|
||||||
|
*/
|
||||||
|
int as14BitInt() const noexcept;
|
||||||
|
|
||||||
|
/** Retrieves the current value mapped to a float between -1.0f and 1.0f. */
|
||||||
|
float asSignedFloat() const noexcept;
|
||||||
|
|
||||||
|
/** Retrieves the current value mapped to a float between 0.0f and 1.0f. */
|
||||||
|
float asUnsignedFloat() const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if two values are equal. */
|
||||||
|
bool operator== (const MPEValue& other) const noexcept;
|
||||||
|
|
||||||
|
/** Returns true if two values are not equal. */
|
||||||
|
bool operator!= (const MPEValue& other) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
MPEValue (int normalisedValue);
|
||||||
|
int normalisedValue = 8192;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
387
modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp
Normal file
387
modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MPEZoneLayout::MPEZoneLayout() noexcept {}
|
||||||
|
|
||||||
|
MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other)
|
||||||
|
: lowerZone (other.lowerZone),
|
||||||
|
upperZone (other.upperZone)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other)
|
||||||
|
{
|
||||||
|
lowerZone = other.lowerZone;
|
||||||
|
upperZone = other.upperZone;
|
||||||
|
|
||||||
|
sendLayoutChangeMessage();
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::sendLayoutChangeMessage()
|
||||||
|
{
|
||||||
|
listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); });
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
|
||||||
|
{
|
||||||
|
checkAndLimitZoneParameters (0, 15, numMemberChannels);
|
||||||
|
checkAndLimitZoneParameters (0, 96, perNotePitchbendRange);
|
||||||
|
checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
|
||||||
|
|
||||||
|
if (isLower)
|
||||||
|
lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||||
|
else
|
||||||
|
upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||||
|
|
||||||
|
if (numMemberChannels > 0)
|
||||||
|
{
|
||||||
|
auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels;
|
||||||
|
|
||||||
|
if (totalChannels >= 15)
|
||||||
|
{
|
||||||
|
if (isLower)
|
||||||
|
upperZone.numMemberChannels = 14 - numMemberChannels;
|
||||||
|
else
|
||||||
|
lowerZone.numMemberChannels = 14 - numMemberChannels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLayoutChangeMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
|
||||||
|
{
|
||||||
|
setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
|
||||||
|
{
|
||||||
|
setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::clearAllZones()
|
||||||
|
{
|
||||||
|
lowerZone = { true, 0 };
|
||||||
|
upperZone = { false, 0 };
|
||||||
|
|
||||||
|
sendLayoutChangeMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message)
|
||||||
|
{
|
||||||
|
if (! message.isController())
|
||||||
|
return;
|
||||||
|
|
||||||
|
MidiRPNMessage rpn;
|
||||||
|
|
||||||
|
if (rpnDetector.parseControllerMessage (message.getChannel(),
|
||||||
|
message.getControllerNumber(),
|
||||||
|
message.getControllerValue(),
|
||||||
|
rpn))
|
||||||
|
{
|
||||||
|
processRpnMessage (rpn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
|
||||||
|
{
|
||||||
|
if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber)
|
||||||
|
processZoneLayoutRpnMessage (rpn);
|
||||||
|
else if (rpn.parameterNumber == 0)
|
||||||
|
processPitchbendRangeRpnMessage (rpn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
|
||||||
|
{
|
||||||
|
if (rpn.value < 16)
|
||||||
|
{
|
||||||
|
if (rpn.channel == 1)
|
||||||
|
setLowerZone (rpn.value);
|
||||||
|
else if (rpn.channel == 16)
|
||||||
|
setUpperZone (rpn.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
|
||||||
|
{
|
||||||
|
if (zone.masterPitchbendRange != value)
|
||||||
|
{
|
||||||
|
checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange);
|
||||||
|
zone.masterPitchbendRange = value;
|
||||||
|
sendLayoutChangeMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value)
|
||||||
|
{
|
||||||
|
if (zone.perNotePitchbendRange != value)
|
||||||
|
{
|
||||||
|
checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange);
|
||||||
|
zone.perNotePitchbendRange = value;
|
||||||
|
sendLayoutChangeMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
|
||||||
|
{
|
||||||
|
if (rpn.channel == 1)
|
||||||
|
{
|
||||||
|
updateMasterPitchbend (lowerZone, rpn.value);
|
||||||
|
}
|
||||||
|
else if (rpn.channel == 16)
|
||||||
|
{
|
||||||
|
updateMasterPitchbend (upperZone, rpn.value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel))
|
||||||
|
updatePerNotePitchbendRange (lowerZone, rpn.value);
|
||||||
|
else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel))
|
||||||
|
updatePerNotePitchbendRange (upperZone, rpn.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer)
|
||||||
|
{
|
||||||
|
MidiBuffer::Iterator iter (buffer);
|
||||||
|
MidiMessage message;
|
||||||
|
int samplePosition; // not actually used, so no need to initialise.
|
||||||
|
|
||||||
|
while (iter.getNextEvent (message, samplePosition))
|
||||||
|
processNextMidiEvent (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
|
||||||
|
{
|
||||||
|
listeners.add (listenerToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
|
||||||
|
{
|
||||||
|
listeners.remove (listenerToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue,
|
||||||
|
int& valueToCheckAndLimit) noexcept
|
||||||
|
{
|
||||||
|
if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue)
|
||||||
|
{
|
||||||
|
// if you hit this, one of the parameters you supplied for this zone
|
||||||
|
// was not within the allowed range!
|
||||||
|
// we fit this back into the allowed range here to maintain a valid
|
||||||
|
// state for the zone, but probably the resulting zone is not what you
|
||||||
|
// wanted it to be!
|
||||||
|
jassertfalse;
|
||||||
|
|
||||||
|
valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
class MPEZoneLayoutTests : public UnitTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", "MIDI/MPE") {}
|
||||||
|
|
||||||
|
void runTest() override
|
||||||
|
{
|
||||||
|
beginTest ("initialisation");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
expect (! layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("adding zones");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
layout.setLowerZone (7);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 7);
|
||||||
|
|
||||||
|
layout.setUpperZone (7);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 7);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 7);
|
||||||
|
|
||||||
|
layout.setLowerZone (3);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 3);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 7);
|
||||||
|
|
||||||
|
layout.setUpperZone (3);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 3);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 3);
|
||||||
|
|
||||||
|
layout.setLowerZone (15);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("clear all zones");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
expect (! layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
|
||||||
|
layout.setLowerZone (7);
|
||||||
|
layout.setUpperZone (2);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
|
||||||
|
layout.clearAllZones();
|
||||||
|
|
||||||
|
expect (! layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("process MIDI buffers");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
MidiBuffer buffer;
|
||||||
|
|
||||||
|
buffer = MPEMessages::setLowerZone (7);
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 7);
|
||||||
|
|
||||||
|
buffer = MPEMessages::setUpperZone (7);
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 7);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 7);
|
||||||
|
|
||||||
|
{
|
||||||
|
buffer = MPEMessages::setLowerZone (10);
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 10);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 4);
|
||||||
|
|
||||||
|
|
||||||
|
buffer = MPEMessages::setLowerZone (10, 33, 44);
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 10);
|
||||||
|
expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
|
||||||
|
expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
buffer = MPEMessages::setUpperZone (10);
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 4);
|
||||||
|
expectEquals (layout.getUpperZone().getMasterChannel(), 16);
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 10);
|
||||||
|
|
||||||
|
buffer = MPEMessages::setUpperZone (10, 33, 44);
|
||||||
|
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expectEquals (layout.getUpperZone().numMemberChannels, 10);
|
||||||
|
expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
|
||||||
|
expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = MPEMessages::clearAllZones();
|
||||||
|
layout.processNextMidiBuffer (buffer);
|
||||||
|
|
||||||
|
expect (! layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTest ("process individual MIDI messages");
|
||||||
|
{
|
||||||
|
MPEZoneLayout layout;
|
||||||
|
|
||||||
|
layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg
|
||||||
|
layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1
|
||||||
|
layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2
|
||||||
|
layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg
|
||||||
|
layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3
|
||||||
|
layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg
|
||||||
|
|
||||||
|
expect (layout.getLowerZone().isActive());
|
||||||
|
expect (! layout.getUpperZone().isActive());
|
||||||
|
expectEquals (layout.getLowerZone().getMasterChannel(), 1);
|
||||||
|
expectEquals (layout.getLowerZone().numMemberChannels, 3);
|
||||||
|
expectEquals (layout.getLowerZone().perNotePitchbendRange, 48);
|
||||||
|
expectEquals (layout.getLowerZone().masterPitchbendRange, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
|
||||||
|
|
||||||
|
|
||||||
|
#endif // JUCE_UNIT_TESTS
|
||||||
|
|
||||||
|
} // namespace juce
|
226
modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h
Normal file
226
modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This class represents the current MPE zone layout of a device capable of handling MPE.
|
||||||
|
|
||||||
|
An MPE device can have up to two zones: a lower zone with master channel 1 and
|
||||||
|
allocated MIDI channels increasing from channel 2, and an upper zone with master
|
||||||
|
channel 16 and allocated MIDI channels decreasing from channel 15. MPE mode is
|
||||||
|
enabled on a device when one of these zones is active and disabled when both
|
||||||
|
are inactive.
|
||||||
|
|
||||||
|
Use the MPEMessages helper class to convert the zone layout represented
|
||||||
|
by this object to MIDI message sequences that you can send to an Expressive
|
||||||
|
MIDI device to set its zone layout, add zones etc.
|
||||||
|
|
||||||
|
@see MPEInstrument
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MPEZoneLayout
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Default constructor.
|
||||||
|
|
||||||
|
This will create a layout with inactive lower and upper zones, representing
|
||||||
|
a device with MPE mode disabled.
|
||||||
|
|
||||||
|
You can set the lower or upper MPE zones using the setZone() method.
|
||||||
|
|
||||||
|
@see setZone
|
||||||
|
*/
|
||||||
|
MPEZoneLayout() noexcept;
|
||||||
|
|
||||||
|
/** Copy constuctor.
|
||||||
|
This will not copy the listeners registered to the MPEZoneLayout.
|
||||||
|
*/
|
||||||
|
MPEZoneLayout (const MPEZoneLayout& other);
|
||||||
|
|
||||||
|
/** Copy assignment operator.
|
||||||
|
This will not copy the listeners registered to the MPEZoneLayout.
|
||||||
|
*/
|
||||||
|
MPEZoneLayout& operator= (const MPEZoneLayout& other);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This struct represents an MPE zone.
|
||||||
|
|
||||||
|
It can either be a lower or an upper zone, where:
|
||||||
|
- A lower zone encompasses master channel 1 and an arbitrary number of ascending
|
||||||
|
MIDI channels, increasing from channel 2.
|
||||||
|
- An upper zone encompasses master channel 16 and an arbitrary number of descending
|
||||||
|
MIDI channels, decreasing from channel 15.
|
||||||
|
|
||||||
|
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and
|
||||||
|
master pitchbends, respectively.
|
||||||
|
*/
|
||||||
|
struct Zone
|
||||||
|
{
|
||||||
|
Zone (const Zone& other) noexcept
|
||||||
|
: numMemberChannels (other.numMemberChannels),
|
||||||
|
perNotePitchbendRange (other.perNotePitchbendRange),
|
||||||
|
masterPitchbendRange (other.masterPitchbendRange),
|
||||||
|
lowerZone (other.lowerZone)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLowerZone() const noexcept { return lowerZone; }
|
||||||
|
bool isUpperZone() const noexcept { return ! lowerZone; }
|
||||||
|
|
||||||
|
bool isActive() const noexcept { return numMemberChannels > 0; }
|
||||||
|
|
||||||
|
int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; }
|
||||||
|
int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; }
|
||||||
|
int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels)
|
||||||
|
: (16 - numMemberChannels); }
|
||||||
|
|
||||||
|
bool isUsingChannelAsMemberChannel (int channel) const noexcept
|
||||||
|
{
|
||||||
|
return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels)
|
||||||
|
: (channel < 16 && channel >= 16 - numMemberChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone
|
||||||
|
&& numMemberChannels == other.numMemberChannels
|
||||||
|
&& perNotePitchbendRange == other.perNotePitchbendRange
|
||||||
|
&& masterPitchbendRange == other.masterPitchbendRange; }
|
||||||
|
|
||||||
|
bool operator!= (const Zone& other) const noexcept { return ! operator== (other); }
|
||||||
|
|
||||||
|
int numMemberChannels;
|
||||||
|
int perNotePitchbendRange;
|
||||||
|
int masterPitchbendRange;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class MPEZoneLayout;
|
||||||
|
|
||||||
|
Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept
|
||||||
|
: numMemberChannels (memberChans),
|
||||||
|
perNotePitchbendRange (perNotePb),
|
||||||
|
masterPitchbendRange (masterPb),
|
||||||
|
lowerZone (lower)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lowerZone;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Sets the lower zone of this layout. */
|
||||||
|
void setLowerZone (int numMemberChannels = 0,
|
||||||
|
int perNotePitchbendRange = 48,
|
||||||
|
int masterPitchbendRange = 2) noexcept;
|
||||||
|
|
||||||
|
/** Sets the upper zone of this layout. */
|
||||||
|
void setUpperZone (int numMemberChannels = 0,
|
||||||
|
int perNotePitchbendRange = 48,
|
||||||
|
int masterPitchbendRange = 2) noexcept;
|
||||||
|
|
||||||
|
/** Returns a struct representing the lower MPE zone. */
|
||||||
|
const Zone getLowerZone() const noexcept { return lowerZone; }
|
||||||
|
|
||||||
|
/** Returns a struct representing the upper MPE zone. */
|
||||||
|
const Zone getUpperZone() const noexcept { return upperZone; }
|
||||||
|
|
||||||
|
/** Clears the lower and upper zones of this layout, making them both inactive
|
||||||
|
and disabling MPE mode.
|
||||||
|
*/
|
||||||
|
void clearAllZones();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Pass incoming MIDI messages to an object of this class if you want the
|
||||||
|
zone layout to properly react to MPE RPN messages like an
|
||||||
|
MPE device.
|
||||||
|
|
||||||
|
MPEMessages::rpnNumber will add or remove zones; RPN 0 will
|
||||||
|
set the per-note or master pitchbend ranges.
|
||||||
|
|
||||||
|
Any other MIDI messages will be ignored by this class.
|
||||||
|
|
||||||
|
@see MPEMessages
|
||||||
|
*/
|
||||||
|
void processNextMidiEvent (const MidiMessage& message);
|
||||||
|
|
||||||
|
/** Pass incoming MIDI buffers to an object of this class if you want the
|
||||||
|
zone layout to properly react to MPE RPN messages like an
|
||||||
|
MPE device.
|
||||||
|
|
||||||
|
MPEMessages::rpnNumber will add or remove zones; RPN 0 will
|
||||||
|
set the per-note or master pitchbend ranges.
|
||||||
|
|
||||||
|
Any other MIDI messages will be ignored by this class.
|
||||||
|
|
||||||
|
@see MPEMessages
|
||||||
|
*/
|
||||||
|
void processNextMidiBuffer (const MidiBuffer& buffer);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Listener class. Derive from this class to allow your class to be
|
||||||
|
notified about changes to the zone layout.
|
||||||
|
*/
|
||||||
|
class Listener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~Listener() {}
|
||||||
|
|
||||||
|
/** Implement this callback to be notified about any changes to this
|
||||||
|
MPEZoneLayout. Will be called whenever a zone is added, zones are
|
||||||
|
removed, or any zone's master or note pitchbend ranges change.
|
||||||
|
*/
|
||||||
|
virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Adds a listener. */
|
||||||
|
void addListener (Listener* const listenerToAdd) noexcept;
|
||||||
|
|
||||||
|
/** Removes a listener. */
|
||||||
|
void removeListener (Listener* const listenerToRemove) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
Zone lowerZone { true, 0 };
|
||||||
|
Zone upperZone { false, 0 };
|
||||||
|
|
||||||
|
MidiRPNDetector rpnDetector;
|
||||||
|
ListenerList<Listener> listeners;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void setZone (bool, int, int, int) noexcept;
|
||||||
|
|
||||||
|
void processRpnMessage (MidiRPNMessage);
|
||||||
|
void processZoneLayoutRpnMessage (MidiRPNMessage);
|
||||||
|
void processPitchbendRangeRpnMessage (MidiRPNMessage);
|
||||||
|
|
||||||
|
void updateMasterPitchbend (Zone&, int);
|
||||||
|
void updatePerNotePitchbendRange (Zone&, int);
|
||||||
|
|
||||||
|
void sendLayoutChangeMessage();
|
||||||
|
void checkAndLimitZoneParameters (int, int, int&) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
330
modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h
Normal file
330
modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
#if ! DOXYGEN && (JUCE_MAC || JUCE_IOS)
|
||||||
|
|
||||||
|
struct CoreAudioLayouts
|
||||||
|
{
|
||||||
|
//==============================================================================
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
coreAudioHOASN3DLayoutTag = (190U<<16) | 0 // kAudioChannelLayoutTag_HOA_ACN_SN3D
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet.
|
||||||
|
|
||||||
|
Note that this method cannot preserve the order of channels.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout)
|
||||||
|
{
|
||||||
|
return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet.
|
||||||
|
|
||||||
|
Note that this method cannot preserve the order of channels.
|
||||||
|
*/
|
||||||
|
static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag)
|
||||||
|
{
|
||||||
|
return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag.
|
||||||
|
|
||||||
|
Note that this method cannot preserve the order of channels.
|
||||||
|
*/
|
||||||
|
static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set)
|
||||||
|
{
|
||||||
|
if (set.getAmbisonicOrder() >= 0)
|
||||||
|
return coreAudioHOASN3DLayoutTag | static_cast<unsigned> (set.size());
|
||||||
|
|
||||||
|
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
|
||||||
|
{
|
||||||
|
AudioChannelSet caSet;
|
||||||
|
|
||||||
|
for (int i = 0; i < numElementsInArray (tbl->channelTypes)
|
||||||
|
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
|
||||||
|
caSet.addChannel (tbl->channelTypes[i]);
|
||||||
|
|
||||||
|
if (caSet == set)
|
||||||
|
return tbl->tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kAudioChannelLayoutTag_DiscreteInOrder | static_cast<AudioChannelLayoutTag> (set.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Array<AudioChannelLayoutTag>& getKnownCoreAudioTags()
|
||||||
|
{
|
||||||
|
static Array<AudioChannelLayoutTag> tags (createKnownCoreAudioTags());
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */
|
||||||
|
static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout)
|
||||||
|
{
|
||||||
|
switch (layout.mChannelLayoutTag & 0xffff0000)
|
||||||
|
{
|
||||||
|
case kAudioChannelLayoutTag_UseChannelBitmap:
|
||||||
|
return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes();
|
||||||
|
case kAudioChannelLayoutTag_UseChannelDescriptions:
|
||||||
|
{
|
||||||
|
Array<AudioChannelSet::ChannelType> channels;
|
||||||
|
|
||||||
|
for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i)
|
||||||
|
channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel));
|
||||||
|
|
||||||
|
// different speaker mappings may point to the same JUCE speaker so fill up
|
||||||
|
// this array with discrete channels
|
||||||
|
for (int j = 0; channels.size() < static_cast<int> (layout.mNumberChannelDescriptions); ++j)
|
||||||
|
channels.addIfNotAlreadyThere (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + j));
|
||||||
|
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
case kAudioChannelLayoutTag_DiscreteInOrder:
|
||||||
|
return AudioChannelSet::discreteChannels (static_cast<int> (layout.mChannelLayoutTag) & 0xffff).getChannelTypes();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag)
|
||||||
|
{
|
||||||
|
// You need to specify the full AudioChannelLayout when using
|
||||||
|
// the UseChannelBitmap and UseChannelDescriptions layout tag
|
||||||
|
jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions);
|
||||||
|
|
||||||
|
Array<AudioChannelSet::ChannelType> speakers;
|
||||||
|
|
||||||
|
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
|
||||||
|
{
|
||||||
|
if (tag == tbl->tag)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numElementsInArray (tbl->channelTypes)
|
||||||
|
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
|
||||||
|
speakers.add (tbl->channelTypes[i]);
|
||||||
|
|
||||||
|
return speakers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto numChannels = tag & 0xffff;
|
||||||
|
if (tag >= coreAudioHOASN3DLayoutTag && tag <= (coreAudioHOASN3DLayoutTag | 0xffff))
|
||||||
|
{
|
||||||
|
auto sqrtMinusOne = std::sqrt (static_cast<float> (numChannels)) - 1.0f;
|
||||||
|
auto ambisonicOrder = jmax (0, static_cast<int> (std::floor (sqrtMinusOne)));
|
||||||
|
|
||||||
|
if (static_cast<float> (ambisonicOrder) == sqrtMinusOne)
|
||||||
|
return AudioChannelSet::ambisonic (ambisonicOrder).getChannelTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UInt32 i = 0; i < numChannels; ++i)
|
||||||
|
speakers.add (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + i));
|
||||||
|
|
||||||
|
return speakers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
struct LayoutTagSpeakerList
|
||||||
|
{
|
||||||
|
AudioChannelLayoutTag tag;
|
||||||
|
AudioChannelSet::ChannelType channelTypes[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
static Array<AudioChannelLayoutTag> createKnownCoreAudioTags()
|
||||||
|
{
|
||||||
|
Array<AudioChannelLayoutTag> tags;
|
||||||
|
|
||||||
|
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
|
||||||
|
tags.addIfNotAlreadyThere (tbl->tag);
|
||||||
|
|
||||||
|
for (unsigned order = 0; order <= 5; ++order)
|
||||||
|
tags.addIfNotAlreadyThere (coreAudioHOASN3DLayoutTag | ((order + 1) * (order + 1)));
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// This list has been derived from https://pastebin.com/24dQ4BPJ
|
||||||
|
// Apple channel labels have been replaced by JUCE channel names
|
||||||
|
// This means that some layouts will be identical in JUCE but not in CoreAudio
|
||||||
|
|
||||||
|
// In Apple's official definition the following tags exist with the same speaker layout and order
|
||||||
|
// even when *not* represented in JUCE channels
|
||||||
|
// kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo
|
||||||
|
// kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal
|
||||||
|
// kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic
|
||||||
|
// kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal
|
||||||
|
struct SpeakerLayoutTable : AudioChannelSet // save us some typing
|
||||||
|
{
|
||||||
|
static LayoutTagSpeakerList* get() noexcept
|
||||||
|
{
|
||||||
|
static LayoutTagSpeakerList tbl[] = {
|
||||||
|
// list layouts for which there is a corresponding named AudioChannelSet first
|
||||||
|
{ kAudioChannelLayoutTag_Mono, { centre } },
|
||||||
|
{ kAudioChannelLayoutTag_Stereo, { left, right } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } },
|
||||||
|
{ kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } },
|
||||||
|
{ kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } },
|
||||||
|
{ kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } },
|
||||||
|
|
||||||
|
// more uncommon layouts
|
||||||
|
{ kAudioChannelLayoutTag_StereoHeadphones, { left, right } },
|
||||||
|
{ kAudioChannelLayoutTag_MatrixStereo, { left, right } },
|
||||||
|
{ kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } },
|
||||||
|
{ kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } },
|
||||||
|
{ kAudioChannelLayoutTag_Binaural, { left, right } },
|
||||||
|
{ kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} },
|
||||||
|
{ kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_4, { left, right, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
|
||||||
|
{ kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
|
||||||
|
{ kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } },
|
||||||
|
{ 0, {} }
|
||||||
|
};
|
||||||
|
|
||||||
|
return tbl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept
|
||||||
|
{
|
||||||
|
if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535)
|
||||||
|
{
|
||||||
|
const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0;
|
||||||
|
return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (label)
|
||||||
|
{
|
||||||
|
case kAudioChannelLabel_Center:
|
||||||
|
case kAudioChannelLabel_Mono: return AudioChannelSet::centre;
|
||||||
|
case kAudioChannelLabel_Left:
|
||||||
|
case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left;
|
||||||
|
case kAudioChannelLabel_Right:
|
||||||
|
case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right;
|
||||||
|
case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE;
|
||||||
|
case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround;
|
||||||
|
case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround;
|
||||||
|
case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre;
|
||||||
|
case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre;
|
||||||
|
case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround;
|
||||||
|
case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide;
|
||||||
|
case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide;
|
||||||
|
case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle;
|
||||||
|
case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft;
|
||||||
|
case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight;
|
||||||
|
case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre;
|
||||||
|
case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft;
|
||||||
|
case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear;
|
||||||
|
case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight;
|
||||||
|
case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear;
|
||||||
|
case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre;
|
||||||
|
case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2;
|
||||||
|
case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft;
|
||||||
|
case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight;
|
||||||
|
case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW;
|
||||||
|
case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX;
|
||||||
|
case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY;
|
||||||
|
case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ;
|
||||||
|
default: return AudioChannelSet::unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace juce
|
181
modules/juce_audio_basics/sources/juce_AudioSource.h
Normal file
181
modules/juce_audio_basics/sources/juce_AudioSource.h
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Used by AudioSource::getNextAudioBlock().
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
struct JUCE_API AudioSourceChannelInfo
|
||||||
|
{
|
||||||
|
/** Creates an uninitialised AudioSourceChannelInfo. */
|
||||||
|
AudioSourceChannelInfo() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an AudioSourceChannelInfo. */
|
||||||
|
AudioSourceChannelInfo (AudioBuffer<float>* bufferToUse,
|
||||||
|
int startSampleOffset, int numSamplesToUse) noexcept
|
||||||
|
: buffer (bufferToUse),
|
||||||
|
startSample (startSampleOffset),
|
||||||
|
numSamples (numSamplesToUse)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an AudioSourceChannelInfo that uses the whole of a buffer.
|
||||||
|
Note that the buffer provided must not be deleted while the
|
||||||
|
AudioSourceChannelInfo is still using it.
|
||||||
|
*/
|
||||||
|
explicit AudioSourceChannelInfo (AudioBuffer<float>& bufferToUse) noexcept
|
||||||
|
: buffer (&bufferToUse),
|
||||||
|
startSample (0),
|
||||||
|
numSamples (bufferToUse.getNumSamples())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The destination buffer to fill with audio data.
|
||||||
|
|
||||||
|
When the AudioSource::getNextAudioBlock() method is called, the active section
|
||||||
|
of this buffer should be filled with whatever output the source produces.
|
||||||
|
|
||||||
|
Only the samples specified by the startSample and numSamples members of this structure
|
||||||
|
should be affected by the call.
|
||||||
|
|
||||||
|
The contents of the buffer when it is passed to the AudioSource::getNextAudioBlock()
|
||||||
|
method can be treated as the input if the source is performing some kind of filter operation,
|
||||||
|
but should be cleared if this is not the case - the clearActiveBufferRegion() is
|
||||||
|
a handy way of doing this.
|
||||||
|
|
||||||
|
The number of channels in the buffer could be anything, so the AudioSource
|
||||||
|
must cope with this in whatever way is appropriate for its function.
|
||||||
|
*/
|
||||||
|
AudioBuffer<float>* buffer;
|
||||||
|
|
||||||
|
/** The first sample in the buffer from which the callback is expected
|
||||||
|
to write data. */
|
||||||
|
int startSample;
|
||||||
|
|
||||||
|
/** The number of samples in the buffer which the callback is expected to
|
||||||
|
fill with data. */
|
||||||
|
int numSamples;
|
||||||
|
|
||||||
|
/** Convenient method to clear the buffer if the source is not producing any data. */
|
||||||
|
void clearActiveBufferRegion() const
|
||||||
|
{
|
||||||
|
if (buffer != nullptr)
|
||||||
|
buffer->clear (startSample, numSamples);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Base class for objects that can produce a continuous stream of audio.
|
||||||
|
|
||||||
|
An AudioSource has two states: 'prepared' and 'unprepared'.
|
||||||
|
|
||||||
|
When a source needs to be played, it is first put into a 'prepared' state by a call to
|
||||||
|
prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to
|
||||||
|
process the audio data.
|
||||||
|
|
||||||
|
Once playback has finished, the releaseResources() method is called to put the stream
|
||||||
|
back into an 'unprepared' state.
|
||||||
|
|
||||||
|
@see AudioFormatReaderSource, ResamplingAudioSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioSource
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates an AudioSource. */
|
||||||
|
AudioSource() noexcept {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~AudioSource() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** 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'
|
||||||
|
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.
|
||||||
|
|
||||||
|
Once playback has finished, the releaseResources() method is called to put the stream
|
||||||
|
back into an 'unprepared' state.
|
||||||
|
|
||||||
|
Note that this method could be called more than once in succession without
|
||||||
|
a matching call to releaseResources(), so make sure your code is robust and
|
||||||
|
can handle that kind of situation.
|
||||||
|
|
||||||
|
@param samplesPerBlockExpected the number of samples that the source
|
||||||
|
will be expected to supply each time its
|
||||||
|
getNextAudioBlock() method is called. This
|
||||||
|
number may vary slightly, because it will be dependent
|
||||||
|
on audio hardware callbacks, and these aren't
|
||||||
|
guaranteed to always use a constant block size, so
|
||||||
|
the source should be able to cope with small variations.
|
||||||
|
@param sampleRate the sample rate that the output will be used at - this
|
||||||
|
is needed by sources such as tone generators.
|
||||||
|
@see releaseResources, getNextAudioBlock
|
||||||
|
*/
|
||||||
|
virtual void prepareToPlay (int samplesPerBlockExpected,
|
||||||
|
double sampleRate) = 0;
|
||||||
|
|
||||||
|
/** Allows the source to release anything it no longer needs after playback has stopped.
|
||||||
|
|
||||||
|
This will be called when the source is no longer going to have its getNextAudioBlock()
|
||||||
|
method called, so it should release any spare memory, etc. that it might have
|
||||||
|
allocated during the prepareToPlay() call.
|
||||||
|
|
||||||
|
Note that there's no guarantee that prepareToPlay() will actually have been called before
|
||||||
|
releaseResources(), and it may be called more than once in succession, so make sure your
|
||||||
|
code is robust and doesn't make any assumptions about when it will be called.
|
||||||
|
|
||||||
|
@see prepareToPlay, getNextAudioBlock
|
||||||
|
*/
|
||||||
|
virtual void releaseResources() = 0;
|
||||||
|
|
||||||
|
/** Called repeatedly to fetch subsequent blocks of audio data.
|
||||||
|
|
||||||
|
After calling the prepareToPlay() method, this callback will be made each
|
||||||
|
time the audio playback hardware (or whatever other destination the audio
|
||||||
|
data is going to) needs another block of data.
|
||||||
|
|
||||||
|
It will generally be called on a high-priority system thread, or possibly even
|
||||||
|
an interrupt, so be careful not to do too much work here, as that will cause
|
||||||
|
audio glitches!
|
||||||
|
|
||||||
|
@see AudioSourceChannelInfo, prepareToPlay, releaseResources
|
||||||
|
*/
|
||||||
|
virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
315
modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp
Normal file
315
modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s,
|
||||||
|
TimeSliceThread& thread,
|
||||||
|
bool deleteSourceWhenDeleted,
|
||||||
|
int bufferSizeSamples,
|
||||||
|
int numChannels,
|
||||||
|
bool prefillBufferOnPrepareToPlay)
|
||||||
|
: source (s, deleteSourceWhenDeleted),
|
||||||
|
backgroundThread (thread),
|
||||||
|
numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)),
|
||||||
|
numberOfChannels (numChannels),
|
||||||
|
prefillBuffer (prefillBufferOnPrepareToPlay)
|
||||||
|
{
|
||||||
|
jassert (source != nullptr);
|
||||||
|
|
||||||
|
jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're
|
||||||
|
// not using a larger buffer..
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferingAudioSource::~BufferingAudioSource()
|
||||||
|
{
|
||||||
|
releaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate)
|
||||||
|
{
|
||||||
|
auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer);
|
||||||
|
|
||||||
|
if (newSampleRate != sampleRate
|
||||||
|
|| bufferSizeNeeded != buffer.getNumSamples()
|
||||||
|
|| ! isPrepared)
|
||||||
|
{
|
||||||
|
backgroundThread.removeTimeSliceClient (this);
|
||||||
|
|
||||||
|
isPrepared = true;
|
||||||
|
sampleRate = newSampleRate;
|
||||||
|
|
||||||
|
source->prepareToPlay (samplesPerBlockExpected, newSampleRate);
|
||||||
|
|
||||||
|
buffer.setSize (numberOfChannels, bufferSizeNeeded);
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
bufferValidStart = 0;
|
||||||
|
bufferValidEnd = 0;
|
||||||
|
|
||||||
|
backgroundThread.addTimeSliceClient (this);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
backgroundThread.moveToFrontOfQueue (this);
|
||||||
|
Thread::sleep (5);
|
||||||
|
}
|
||||||
|
while (prefillBuffer
|
||||||
|
&& (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferingAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
isPrepared = false;
|
||||||
|
backgroundThread.removeTimeSliceClient (this);
|
||||||
|
|
||||||
|
buffer.setSize (numberOfChannels, 0);
|
||||||
|
|
||||||
|
// MSVC2015 seems to need this if statement to not generate a warning during linking.
|
||||||
|
// As source is set in the constructor, there is no way that source could
|
||||||
|
// ever equal this, but it seems to make MSVC2015 happy.
|
||||||
|
if (source != this)
|
||||||
|
source->releaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (bufferStartPosLock);
|
||||||
|
|
||||||
|
auto start = bufferValidStart.load();
|
||||||
|
auto end = bufferValidEnd.load();
|
||||||
|
auto pos = nextPlayPos.load();
|
||||||
|
|
||||||
|
auto validStart = (int) (jlimit (start, end, pos) - pos);
|
||||||
|
auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos);
|
||||||
|
|
||||||
|
if (validStart == validEnd)
|
||||||
|
{
|
||||||
|
// total cache miss
|
||||||
|
info.clearActiveBufferRegion();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (validStart > 0)
|
||||||
|
info.buffer->clear (info.startSample, validStart); // partial cache miss at start
|
||||||
|
|
||||||
|
if (validEnd < info.numSamples)
|
||||||
|
info.buffer->clear (info.startSample + validEnd,
|
||||||
|
info.numSamples - validEnd); // partial cache miss at end
|
||||||
|
|
||||||
|
if (validStart < validEnd)
|
||||||
|
{
|
||||||
|
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
|
||||||
|
{
|
||||||
|
jassert (buffer.getNumSamples() > 0);
|
||||||
|
auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
|
||||||
|
auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
|
||||||
|
|
||||||
|
if (startBufferIndex < endBufferIndex)
|
||||||
|
{
|
||||||
|
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||||
|
buffer,
|
||||||
|
chan, startBufferIndex,
|
||||||
|
validEnd - validStart);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto initialSize = buffer.getNumSamples() - startBufferIndex;
|
||||||
|
|
||||||
|
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||||
|
buffer,
|
||||||
|
chan, startBufferIndex,
|
||||||
|
initialSize);
|
||||||
|
|
||||||
|
info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
|
||||||
|
buffer,
|
||||||
|
chan, 0,
|
||||||
|
(validEnd - validStart) - initialSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPlayPos += info.numSamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout)
|
||||||
|
{
|
||||||
|
if (!source || source->getTotalLength() <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (nextPlayPos + info.numSamples < 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (! isLooping() && nextPlayPos > getTotalLength())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto now = Time::getMillisecondCounter();
|
||||||
|
auto startTime = now;
|
||||||
|
|
||||||
|
auto elapsed = (now >= startTime ? now - startTime
|
||||||
|
: (std::numeric_limits<uint32>::max() - startTime) + now);
|
||||||
|
|
||||||
|
while (elapsed <= timeout)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const ScopedLock sl (bufferStartPosLock);
|
||||||
|
|
||||||
|
auto start = bufferValidStart.load();
|
||||||
|
auto end = bufferValidEnd.load();
|
||||||
|
auto pos = nextPlayPos.load();
|
||||||
|
|
||||||
|
auto validStart = static_cast<int> (jlimit (start, end, pos) - pos);
|
||||||
|
auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos);
|
||||||
|
|
||||||
|
if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
now = Time::getMillisecondCounter();
|
||||||
|
elapsed = (now >= startTime ? now - startTime
|
||||||
|
: (std::numeric_limits<uint32>::max() - startTime) + now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 BufferingAudioSource::getNextReadPosition() const
|
||||||
|
{
|
||||||
|
jassert (source->getTotalLength() > 0);
|
||||||
|
auto pos = nextPlayPos.load();
|
||||||
|
|
||||||
|
return (source->isLooping() && nextPlayPos > 0)
|
||||||
|
? pos % source->getTotalLength()
|
||||||
|
: pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferingAudioSource::setNextReadPosition (int64 newPosition)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (bufferStartPosLock);
|
||||||
|
|
||||||
|
nextPlayPos = newPosition;
|
||||||
|
backgroundThread.moveToFrontOfQueue (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferingAudioSource::readNextBufferChunk()
|
||||||
|
{
|
||||||
|
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (bufferStartPosLock);
|
||||||
|
|
||||||
|
if (wasSourceLooping != isLooping())
|
||||||
|
{
|
||||||
|
wasSourceLooping = isLooping();
|
||||||
|
bufferValidStart = 0;
|
||||||
|
bufferValidEnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
newBVS = jmax ((int64) 0, nextPlayPos.load());
|
||||||
|
newBVE = newBVS + buffer.getNumSamples() - 4;
|
||||||
|
sectionToReadStart = 0;
|
||||||
|
sectionToReadEnd = 0;
|
||||||
|
|
||||||
|
const int maxChunkSize = 2048;
|
||||||
|
|
||||||
|
if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
|
||||||
|
{
|
||||||
|
newBVE = jmin (newBVE, newBVS + maxChunkSize);
|
||||||
|
|
||||||
|
sectionToReadStart = newBVS;
|
||||||
|
sectionToReadEnd = newBVE;
|
||||||
|
|
||||||
|
bufferValidStart = 0;
|
||||||
|
bufferValidEnd = 0;
|
||||||
|
}
|
||||||
|
else if (std::abs ((int) (newBVS - bufferValidStart)) > 512
|
||||||
|
|| std::abs ((int) (newBVE - bufferValidEnd)) > 512)
|
||||||
|
{
|
||||||
|
newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize);
|
||||||
|
|
||||||
|
sectionToReadStart = bufferValidEnd;
|
||||||
|
sectionToReadEnd = newBVE;
|
||||||
|
|
||||||
|
bufferValidStart = newBVS;
|
||||||
|
bufferValidEnd = jmin (bufferValidEnd.load(), newBVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionToReadStart == sectionToReadEnd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
jassert (buffer.getNumSamples() > 0);
|
||||||
|
auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
|
||||||
|
auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
|
||||||
|
|
||||||
|
if (bufferIndexStart < bufferIndexEnd)
|
||||||
|
{
|
||||||
|
readBufferSection (sectionToReadStart,
|
||||||
|
(int) (sectionToReadEnd - sectionToReadStart),
|
||||||
|
bufferIndexStart);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto initialSize = buffer.getNumSamples() - bufferIndexStart;
|
||||||
|
|
||||||
|
readBufferSection (sectionToReadStart,
|
||||||
|
initialSize,
|
||||||
|
bufferIndexStart);
|
||||||
|
|
||||||
|
readBufferSection (sectionToReadStart + initialSize,
|
||||||
|
(int) (sectionToReadEnd - sectionToReadStart) - initialSize,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl2 (bufferStartPosLock);
|
||||||
|
|
||||||
|
bufferValidStart = newBVS;
|
||||||
|
bufferValidEnd = newBVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferReadyEvent.signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset)
|
||||||
|
{
|
||||||
|
if (source->getNextReadPosition() != start)
|
||||||
|
source->setNextReadPosition (start);
|
||||||
|
|
||||||
|
AudioSourceChannelInfo info (&buffer, bufferOffset, length);
|
||||||
|
source->getNextAudioBlock (info);
|
||||||
|
}
|
||||||
|
|
||||||
|
int BufferingAudioSource::useTimeSlice()
|
||||||
|
{
|
||||||
|
return readNextBufferChunk() ? 1 : 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
119
modules/juce_audio_basics/sources/juce_BufferingAudioSource.h
Normal file
119
modules/juce_audio_basics/sources/juce_BufferingAudioSource.h
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource which takes another source as input, and buffers it using a thread.
|
||||||
|
|
||||||
|
Create this as a wrapper around another thread, and it will read-ahead with
|
||||||
|
a background thread to smooth out playback. You can either create one of these
|
||||||
|
directly, or use it indirectly using an AudioTransportSource.
|
||||||
|
|
||||||
|
@see PositionableAudioSource, AudioTransportSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API BufferingAudioSource : public PositionableAudioSource,
|
||||||
|
private TimeSliceClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a BufferingAudioSource.
|
||||||
|
|
||||||
|
@param source the input source to read from
|
||||||
|
@param backgroundThread a background thread that will be used for the
|
||||||
|
background read-ahead. This object must not be deleted
|
||||||
|
until after any BufferingAudioSources that are using it
|
||||||
|
have been deleted!
|
||||||
|
@param deleteSourceWhenDeleted if true, then the input source object will
|
||||||
|
be deleted when this object is deleted
|
||||||
|
@param numberOfSamplesToBuffer the size of buffer to use for reading ahead
|
||||||
|
@param numberOfChannels the number of channels that will be played
|
||||||
|
@param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will
|
||||||
|
block until the buffer has been filled
|
||||||
|
*/
|
||||||
|
BufferingAudioSource (PositionableAudioSource* source,
|
||||||
|
TimeSliceThread& backgroundThread,
|
||||||
|
bool deleteSourceWhenDeleted,
|
||||||
|
int numberOfSamplesToBuffer,
|
||||||
|
int numberOfChannels = 2,
|
||||||
|
bool prefillBufferOnPrepareToPlay = true);
|
||||||
|
|
||||||
|
/** Destructor.
|
||||||
|
|
||||||
|
The input source may be deleted depending on whether the deleteSourceWhenDeleted
|
||||||
|
flag was set in the constructor.
|
||||||
|
*/
|
||||||
|
~BufferingAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Implements the PositionableAudioSource method. */
|
||||||
|
void setNextReadPosition (int64 newPosition) override;
|
||||||
|
|
||||||
|
/** Implements the PositionableAudioSource method. */
|
||||||
|
int64 getNextReadPosition() const override;
|
||||||
|
|
||||||
|
/** Implements the PositionableAudioSource method. */
|
||||||
|
int64 getTotalLength() const override { return source->getTotalLength(); }
|
||||||
|
|
||||||
|
/** Implements the PositionableAudioSource method. */
|
||||||
|
bool isLooping() const override { return source->isLooping(); }
|
||||||
|
|
||||||
|
/** A useful function to block until the next the buffer info can be filled.
|
||||||
|
|
||||||
|
This is useful for offline rendering.
|
||||||
|
*/
|
||||||
|
bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout);
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OptionalScopedPointer<PositionableAudioSource> source;
|
||||||
|
TimeSliceThread& backgroundThread;
|
||||||
|
int numberOfSamplesToBuffer, numberOfChannels;
|
||||||
|
AudioBuffer<float> buffer;
|
||||||
|
CriticalSection bufferStartPosLock;
|
||||||
|
WaitableEvent bufferReadyEvent;
|
||||||
|
std::atomic<int64> bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 };
|
||||||
|
double sampleRate = 0;
|
||||||
|
bool wasSourceLooping = false, isPrepared = false, prefillBuffer;
|
||||||
|
|
||||||
|
bool readNextBufferChunk();
|
||||||
|
void readBufferSection (int64 start, int length, int bufferOffset);
|
||||||
|
int useTimeSlice() override;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_,
|
||||||
|
const bool deleteSourceWhenDeleted)
|
||||||
|
: source (source_, deleteSourceWhenDeleted),
|
||||||
|
requiredNumberOfChannels (2)
|
||||||
|
{
|
||||||
|
remappedInfo.buffer = &buffer;
|
||||||
|
remappedInfo.startSample = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
requiredNumberOfChannels = requiredNumberOfChannels_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::clearAllMappings()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
remappedInputs.clear();
|
||||||
|
remappedOutputs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
while (remappedInputs.size() < destIndex)
|
||||||
|
remappedInputs.add (-1);
|
||||||
|
|
||||||
|
remappedInputs.set (destIndex, sourceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
while (remappedOutputs.size() < sourceIndex)
|
||||||
|
remappedOutputs.add (-1);
|
||||||
|
|
||||||
|
remappedOutputs.set (sourceIndex, destIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size())
|
||||||
|
return remappedInputs.getUnchecked (inputChannelIndex);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size())
|
||||||
|
return remappedOutputs .getUnchecked (outputChannelIndex);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||||
|
{
|
||||||
|
source->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
source->releaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true);
|
||||||
|
|
||||||
|
const int numChans = bufferToFill.buffer->getNumChannels();
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.getNumChannels(); ++i)
|
||||||
|
{
|
||||||
|
const int remappedChan = getRemappedInputChannel (i);
|
||||||
|
|
||||||
|
if (remappedChan >= 0 && remappedChan < numChans)
|
||||||
|
{
|
||||||
|
buffer.copyFrom (i, 0, *bufferToFill.buffer,
|
||||||
|
remappedChan,
|
||||||
|
bufferToFill.startSample,
|
||||||
|
bufferToFill.numSamples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.clear (i, 0, bufferToFill.numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remappedInfo.numSamples = bufferToFill.numSamples;
|
||||||
|
|
||||||
|
source->getNextAudioBlock (remappedInfo);
|
||||||
|
|
||||||
|
bufferToFill.clearActiveBufferRegion();
|
||||||
|
|
||||||
|
for (int i = 0; i < requiredNumberOfChannels; ++i)
|
||||||
|
{
|
||||||
|
const int remappedChan = getRemappedOutputChannel (i);
|
||||||
|
|
||||||
|
if (remappedChan >= 0 && remappedChan < numChans)
|
||||||
|
{
|
||||||
|
bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample,
|
||||||
|
buffer, i, 0, bufferToFill.numSamples);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
XmlElement* ChannelRemappingAudioSource::createXml() const
|
||||||
|
{
|
||||||
|
XmlElement* e = new XmlElement ("MAPPINGS");
|
||||||
|
String ins, outs;
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (int i = 0; i < remappedInputs.size(); ++i)
|
||||||
|
ins << remappedInputs.getUnchecked(i) << ' ';
|
||||||
|
|
||||||
|
for (int i = 0; i < remappedOutputs.size(); ++i)
|
||||||
|
outs << remappedOutputs.getUnchecked(i) << ' ';
|
||||||
|
|
||||||
|
e->setAttribute ("inputs", ins.trimEnd());
|
||||||
|
e->setAttribute ("outputs", outs.trimEnd());
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e)
|
||||||
|
{
|
||||||
|
if (e.hasTagName ("MAPPINGS"))
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
clearAllMappings();
|
||||||
|
|
||||||
|
StringArray ins, outs;
|
||||||
|
ins.addTokens (e.getStringAttribute ("inputs"), false);
|
||||||
|
outs.addTokens (e.getStringAttribute ("outputs"), false);
|
||||||
|
|
||||||
|
for (int i = 0; i < ins.size(); ++i)
|
||||||
|
remappedInputs.add (ins[i].getIntValue());
|
||||||
|
|
||||||
|
for (int i = 0; i < outs.size(); ++i)
|
||||||
|
remappedOutputs.add (outs[i].getIntValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource that takes the audio from another source, and re-maps its
|
||||||
|
input and output channels to a different arrangement.
|
||||||
|
|
||||||
|
You can use this to increase or decrease the number of channels that an
|
||||||
|
audio source uses, or to re-order those channels.
|
||||||
|
|
||||||
|
Call the reset() method before using it to set up a default mapping, and then
|
||||||
|
the setInputChannelMapping() and setOutputChannelMapping() methods to
|
||||||
|
create an appropriate mapping, otherwise no channels will be connected and
|
||||||
|
it'll produce silence.
|
||||||
|
|
||||||
|
@see AudioSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class ChannelRemappingAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a remapping source that will pass on audio from the given input.
|
||||||
|
|
||||||
|
@param source the input source to use. Make sure that this doesn't
|
||||||
|
get deleted before the ChannelRemappingAudioSource object
|
||||||
|
@param deleteSourceWhenDeleted if true, the input source will be deleted
|
||||||
|
when this object is deleted, if false, the caller is
|
||||||
|
responsible for its deletion
|
||||||
|
*/
|
||||||
|
ChannelRemappingAudioSource (AudioSource* source,
|
||||||
|
bool deleteSourceWhenDeleted);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~ChannelRemappingAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Specifies a number of channels that this audio source must produce from its
|
||||||
|
getNextAudioBlock() callback.
|
||||||
|
*/
|
||||||
|
void setNumberOfChannelsToProduce (int requiredNumberOfChannels);
|
||||||
|
|
||||||
|
/** Clears any mapped channels.
|
||||||
|
|
||||||
|
After this, no channels are mapped, so this object will produce silence. Create
|
||||||
|
some mappings with setInputChannelMapping() and setOutputChannelMapping().
|
||||||
|
*/
|
||||||
|
void clearAllMappings();
|
||||||
|
|
||||||
|
/** Creates an input channel mapping.
|
||||||
|
|
||||||
|
When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming
|
||||||
|
data will be sent to destChannelIndex of our input source.
|
||||||
|
|
||||||
|
@param destChannelIndex the index of an input channel in our input audio source (i.e. the
|
||||||
|
source specified when this object was created).
|
||||||
|
@param sourceChannelIndex the index of the input channel in the incoming audio data buffer
|
||||||
|
during our getNextAudioBlock() callback
|
||||||
|
*/
|
||||||
|
void setInputChannelMapping (int destChannelIndex,
|
||||||
|
int sourceChannelIndex);
|
||||||
|
|
||||||
|
/** Creates an output channel mapping.
|
||||||
|
|
||||||
|
When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by
|
||||||
|
our input audio source will be copied to channel destChannelIndex of the final buffer.
|
||||||
|
|
||||||
|
@param sourceChannelIndex the index of an output channel coming from our input audio source
|
||||||
|
(i.e. the source specified when this object was created).
|
||||||
|
@param destChannelIndex the index of the output channel in the incoming audio data buffer
|
||||||
|
during our getNextAudioBlock() callback
|
||||||
|
*/
|
||||||
|
void setOutputChannelMapping (int sourceChannelIndex,
|
||||||
|
int destChannelIndex);
|
||||||
|
|
||||||
|
/** Returns the channel from our input that will be sent to channel inputChannelIndex of
|
||||||
|
our input audio source.
|
||||||
|
*/
|
||||||
|
int getRemappedInputChannel (int inputChannelIndex) const;
|
||||||
|
|
||||||
|
/** Returns the output channel to which channel outputChannelIndex of our input audio
|
||||||
|
source will be sent to.
|
||||||
|
*/
|
||||||
|
int getRemappedOutputChannel (int outputChannelIndex) const;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns an XML object to encapsulate the state of the mappings.
|
||||||
|
@see restoreFromXml
|
||||||
|
*/
|
||||||
|
XmlElement* createXml() const;
|
||||||
|
|
||||||
|
/** Restores the mappings from an XML object created by createXML().
|
||||||
|
@see createXml
|
||||||
|
*/
|
||||||
|
void restoreFromXml (const XmlElement&);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OptionalScopedPointer<AudioSource> source;
|
||||||
|
Array<int> remappedInputs, remappedOutputs;
|
||||||
|
int requiredNumberOfChannels;
|
||||||
|
|
||||||
|
AudioBuffer<float> buffer;
|
||||||
|
AudioSourceChannelInfo remappedInfo;
|
||||||
|
CriticalSection lock;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -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.
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource,
|
||||||
|
const bool deleteInputWhenDeleted)
|
||||||
|
: input (inputSource, deleteInputWhenDeleted)
|
||||||
|
{
|
||||||
|
jassert (inputSource != nullptr);
|
||||||
|
|
||||||
|
for (int i = 2; --i >= 0;)
|
||||||
|
iirFilters.add (new IIRFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRFilterAudioSource::~IIRFilterAudioSource() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients)
|
||||||
|
{
|
||||||
|
for (int i = iirFilters.size(); --i >= 0;)
|
||||||
|
iirFilters.getUnchecked(i)->setCoefficients (newCoefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIRFilterAudioSource::makeInactive()
|
||||||
|
{
|
||||||
|
for (int i = iirFilters.size(); --i >= 0;)
|
||||||
|
iirFilters.getUnchecked(i)->makeInactive();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||||
|
{
|
||||||
|
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||||
|
|
||||||
|
for (int i = iirFilters.size(); --i >= 0;)
|
||||||
|
iirFilters.getUnchecked(i)->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIRFilterAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
input->releaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||||
|
{
|
||||||
|
input->getNextAudioBlock (bufferToFill);
|
||||||
|
|
||||||
|
const int numChannels = bufferToFill.buffer->getNumChannels();
|
||||||
|
|
||||||
|
while (numChannels > iirFilters.size())
|
||||||
|
iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0)));
|
||||||
|
|
||||||
|
for (int i = 0; i < numChannels; ++i)
|
||||||
|
iirFilters.getUnchecked(i)
|
||||||
|
->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample),
|
||||||
|
bufferToFill.numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource that performs an IIR filter on another source.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API IIRFilterAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a IIRFilterAudioSource for a given input source.
|
||||||
|
|
||||||
|
@param inputSource the input source to read from - this must not be null
|
||||||
|
@param deleteInputWhenDeleted if true, the input source will be deleted when
|
||||||
|
this object is deleted
|
||||||
|
*/
|
||||||
|
IIRFilterAudioSource (AudioSource* inputSource,
|
||||||
|
bool deleteInputWhenDeleted);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~IIRFilterAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Changes the filter to use the same parameters as the one being passed in. */
|
||||||
|
void setCoefficients (const IIRCoefficients& newCoefficients);
|
||||||
|
|
||||||
|
/** Calls IIRFilter::makeInactive() on all the filters being used internally. */
|
||||||
|
void makeInactive();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OptionalScopedPointer<AudioSource> input;
|
||||||
|
OwnedArray<IIRFilter> iirFilters;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
70
modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp
Normal file
70
modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MemoryAudioSource::MemoryAudioSource (AudioBuffer<float>& bufferToUse, bool copyMemory, bool shouldLoop)
|
||||||
|
: isLooping (shouldLoop)
|
||||||
|
{
|
||||||
|
if (copyMemory)
|
||||||
|
buffer.makeCopyOf (bufferToUse);
|
||||||
|
else
|
||||||
|
buffer.setDataToReferTo (bufferToUse.getArrayOfWritePointers(),
|
||||||
|
bufferToUse.getNumChannels(),
|
||||||
|
bufferToUse.getNumSamples());
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MemoryAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/)
|
||||||
|
{
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryAudioSource::releaseResources() {}
|
||||||
|
|
||||||
|
void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||||
|
{
|
||||||
|
auto& dst = *bufferToFill.buffer;
|
||||||
|
auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels());
|
||||||
|
auto max = 0, pos = 0;
|
||||||
|
auto n = buffer.getNumSamples(), m = bufferToFill.numSamples;
|
||||||
|
|
||||||
|
for (auto i = position; (i < n || isLooping) && (pos < m); i += max)
|
||||||
|
{
|
||||||
|
max = jmin (m - pos, n - (i % n));
|
||||||
|
|
||||||
|
int ch = 0;
|
||||||
|
for (; ch < channels; ++ch)
|
||||||
|
dst.copyFrom (ch, bufferToFill.startSample + pos, buffer, ch, i % n, max);
|
||||||
|
|
||||||
|
for (; ch < dst.getNumChannels(); ++ch)
|
||||||
|
dst.clear (ch, bufferToFill.startSample + pos, max);
|
||||||
|
|
||||||
|
pos += max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos < m)
|
||||||
|
dst.clear (bufferToFill.startSample + pos, m - pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
65
modules/juce_audio_basics/sources/juce_MemoryAudioSource.h
Normal file
65
modules/juce_audio_basics/sources/juce_MemoryAudioSource.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource which takes some float audio data as an input.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MemoryAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a MemoryAudioSource by providing an audio buffer.
|
||||||
|
|
||||||
|
If copyMemory is true then the buffer will be copied into an internal
|
||||||
|
buffer which will be owned by the MemoryAudioSource. If copyMemory is
|
||||||
|
false, then you must ensure that the lifetime of the audio buffer is
|
||||||
|
at least as long as the MemoryAudioSource.
|
||||||
|
*/
|
||||||
|
MemoryAudioSource (AudioBuffer<float>& audioBuffer, bool copyMemory, bool shouldLoop = false);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
AudioBuffer<float> buffer;
|
||||||
|
int position = 0;
|
||||||
|
bool isLooping;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
158
modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp
Normal file
158
modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MixerAudioSource::MixerAudioSource()
|
||||||
|
: currentSampleRate (0.0), bufferSizeExpected (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MixerAudioSource::~MixerAudioSource()
|
||||||
|
{
|
||||||
|
removeAllInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved)
|
||||||
|
{
|
||||||
|
if (input != nullptr && ! inputs.contains (input))
|
||||||
|
{
|
||||||
|
double localRate;
|
||||||
|
int localBufferSize;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
localRate = currentSampleRate;
|
||||||
|
localBufferSize = bufferSizeExpected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localRate > 0.0)
|
||||||
|
input->prepareToPlay (localBufferSize, localRate);
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
inputsToDelete.setBit (inputs.size(), deleteWhenRemoved);
|
||||||
|
inputs.add (input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAudioSource::removeInputSource (AudioSource* const input)
|
||||||
|
{
|
||||||
|
if (input != nullptr)
|
||||||
|
{
|
||||||
|
std::unique_ptr<AudioSource> toDelete;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
const int index = inputs.indexOf (input);
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (inputsToDelete [index])
|
||||||
|
toDelete.reset (input);
|
||||||
|
|
||||||
|
inputsToDelete.shiftBits (-1, index);
|
||||||
|
inputs.remove (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
input->releaseResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAudioSource::removeAllInputs()
|
||||||
|
{
|
||||||
|
OwnedArray<AudioSource> toDelete;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (int i = inputs.size(); --i >= 0;)
|
||||||
|
if (inputsToDelete[i])
|
||||||
|
toDelete.add (inputs.getUnchecked(i));
|
||||||
|
|
||||||
|
inputs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = toDelete.size(); --i >= 0;)
|
||||||
|
toDelete.getUnchecked(i)->releaseResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||||
|
{
|
||||||
|
tempBuffer.setSize (2, samplesPerBlockExpected);
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
currentSampleRate = sampleRate;
|
||||||
|
bufferSizeExpected = samplesPerBlockExpected;
|
||||||
|
|
||||||
|
for (int i = inputs.size(); --i >= 0;)
|
||||||
|
inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (int i = inputs.size(); --i >= 0;)
|
||||||
|
inputs.getUnchecked(i)->releaseResources();
|
||||||
|
|
||||||
|
tempBuffer.setSize (2, 0);
|
||||||
|
|
||||||
|
currentSampleRate = 0;
|
||||||
|
bufferSizeExpected = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (inputs.size() > 0)
|
||||||
|
{
|
||||||
|
inputs.getUnchecked(0)->getNextAudioBlock (info);
|
||||||
|
|
||||||
|
if (inputs.size() > 1)
|
||||||
|
{
|
||||||
|
tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()),
|
||||||
|
info.buffer->getNumSamples());
|
||||||
|
|
||||||
|
AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples);
|
||||||
|
|
||||||
|
for (int i = 1; i < inputs.size(); ++i)
|
||||||
|
{
|
||||||
|
inputs.getUnchecked(i)->getNextAudioBlock (info2);
|
||||||
|
|
||||||
|
for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan)
|
||||||
|
info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.clearActiveBufferRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
99
modules/juce_audio_basics/sources/juce_MixerAudioSource.h
Normal file
99
modules/juce_audio_basics/sources/juce_MixerAudioSource.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource that mixes together the output of a set of other AudioSources.
|
||||||
|
|
||||||
|
Input sources can be added and removed while the mixer is running as long as their
|
||||||
|
prepareToPlay() and releaseResources() methods are called before and after adding
|
||||||
|
them to the mixer.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MixerAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a MixerAudioSource. */
|
||||||
|
MixerAudioSource();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MixerAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Adds an input source to the mixer.
|
||||||
|
|
||||||
|
If the mixer is running you'll need to make sure that the input source
|
||||||
|
is ready to play by calling its prepareToPlay() method before adding it.
|
||||||
|
If the mixer is stopped, then its input sources will be automatically
|
||||||
|
prepared when the mixer's prepareToPlay() method is called.
|
||||||
|
|
||||||
|
@param newInput the source to add to the mixer
|
||||||
|
@param deleteWhenRemoved if true, then this source will be deleted when
|
||||||
|
no longer needed by the mixer.
|
||||||
|
*/
|
||||||
|
void addInputSource (AudioSource* newInput, bool deleteWhenRemoved);
|
||||||
|
|
||||||
|
/** Removes an input source.
|
||||||
|
If the source was added by calling addInputSource() with the deleteWhenRemoved
|
||||||
|
flag set, it will be deleted by this method.
|
||||||
|
*/
|
||||||
|
void removeInputSource (AudioSource* input);
|
||||||
|
|
||||||
|
/** Removes all the input sources.
|
||||||
|
Any sources which were added by calling addInputSource() with the deleteWhenRemoved
|
||||||
|
flag set will be deleted by this method.
|
||||||
|
*/
|
||||||
|
void removeAllInputs();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Implementation of the AudioSource method.
|
||||||
|
This will call prepareToPlay() on all its input sources.
|
||||||
|
*/
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method.
|
||||||
|
This will call releaseResources() on all its input sources.
|
||||||
|
*/
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
Array<AudioSource*> inputs;
|
||||||
|
BigInteger inputsToDelete;
|
||||||
|
CriticalSection lock;
|
||||||
|
AudioBuffer<float> tempBuffer;
|
||||||
|
double currentSampleRate;
|
||||||
|
int bufferSizeExpected;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 type of AudioSource which can be repositioned.
|
||||||
|
|
||||||
|
The basic AudioSource just streams continuously with no idea of a current
|
||||||
|
time or length, so the PositionableAudioSource is used for a finite stream
|
||||||
|
that has a current read position.
|
||||||
|
|
||||||
|
@see AudioSource, AudioTransportSource
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API PositionableAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates the PositionableAudioSource. */
|
||||||
|
PositionableAudioSource() noexcept {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Destructor */
|
||||||
|
~PositionableAudioSource() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Tells the stream to move to a new position.
|
||||||
|
|
||||||
|
Calling this indicates that the next call to AudioSource::getNextAudioBlock()
|
||||||
|
should return samples from this position.
|
||||||
|
|
||||||
|
Note that this may be called on a different thread to getNextAudioBlock(),
|
||||||
|
so the subclass should make sure it's synchronised.
|
||||||
|
*/
|
||||||
|
virtual void setNextReadPosition (int64 newPosition) = 0;
|
||||||
|
|
||||||
|
/** Returns the position from which the next block will be returned.
|
||||||
|
|
||||||
|
@see setNextReadPosition
|
||||||
|
*/
|
||||||
|
virtual int64 getNextReadPosition() const = 0;
|
||||||
|
|
||||||
|
/** Returns the total length of the stream (in samples). */
|
||||||
|
virtual int64 getTotalLength() const = 0;
|
||||||
|
|
||||||
|
/** Returns true if this source is actually playing in a loop. */
|
||||||
|
virtual bool isLooping() const = 0;
|
||||||
|
|
||||||
|
/** Tells the source whether you'd like it to play in a loop. */
|
||||||
|
virtual void setLooping (bool shouldLoop) { ignoreUnused (shouldLoop); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
266
modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp
Normal file
266
modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
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);
|
||||||
|
zeromem (coefficients, sizeof (coefficients));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResamplingAudioSource::~ResamplingAudioSource() {}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample)
|
||||||
|
{
|
||||||
|
jassert (samplesInPerOutputSample > 0);
|
||||||
|
|
||||||
|
const SpinLock::ScopedLockType sl (ratioLock);
|
||||||
|
ratio = jmax (0.0, samplesInPerOutputSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (ratioLock);
|
||||||
|
|
||||||
|
auto scaledBlockSize = roundToInt (samplesPerBlockExpected * ratio);
|
||||||
|
input->prepareToPlay (scaledBlockSize, sampleRate * ratio);
|
||||||
|
|
||||||
|
buffer.setSize (numChannels, scaledBlockSize + 32);
|
||||||
|
|
||||||
|
filterStates.calloc (numChannels);
|
||||||
|
srcBuffers.calloc (numChannels);
|
||||||
|
destBuffers.calloc (numChannels);
|
||||||
|
createLowPass (ratio);
|
||||||
|
|
||||||
|
flushBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::flushBuffers()
|
||||||
|
{
|
||||||
|
buffer.clear();
|
||||||
|
bufferPos = 0;
|
||||||
|
sampsInBuffer = 0;
|
||||||
|
subSampleOffset = 0.0;
|
||||||
|
resetFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
input->releaseResources();
|
||||||
|
buffer.setSize (numChannels, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||||
|
{
|
||||||
|
double localRatio;
|
||||||
|
|
||||||
|
{
|
||||||
|
const SpinLock::ScopedLockType sl (ratioLock);
|
||||||
|
localRatio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastRatio != localRatio)
|
||||||
|
{
|
||||||
|
createLowPass (localRatio);
|
||||||
|
lastRatio = localRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 3;
|
||||||
|
|
||||||
|
int bufferSize = buffer.getNumSamples();
|
||||||
|
|
||||||
|
if (bufferSize < sampsNeeded + 8)
|
||||||
|
{
|
||||||
|
bufferPos %= bufferSize;
|
||||||
|
bufferSize = sampsNeeded + 32;
|
||||||
|
buffer.setSize (buffer.getNumChannels(), bufferSize, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferPos %= bufferSize;
|
||||||
|
|
||||||
|
int endOfBufferPos = bufferPos + sampsInBuffer;
|
||||||
|
const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels());
|
||||||
|
|
||||||
|
while (sampsNeeded > sampsInBuffer)
|
||||||
|
{
|
||||||
|
endOfBufferPos %= bufferSize;
|
||||||
|
|
||||||
|
int numToDo = jmin (sampsNeeded - sampsInBuffer,
|
||||||
|
bufferSize - endOfBufferPos);
|
||||||
|
|
||||||
|
AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo);
|
||||||
|
input->getNextAudioBlock (readInfo);
|
||||||
|
|
||||||
|
if (localRatio > 1.0001)
|
||||||
|
{
|
||||||
|
// for down-sampling, pre-apply the filter..
|
||||||
|
|
||||||
|
for (int i = channelsToProcess; --i >= 0;)
|
||||||
|
applyFilter (buffer.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sampsInBuffer += numToDo;
|
||||||
|
endOfBufferPos += numToDo;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int channel = 0; channel < channelsToProcess; ++channel)
|
||||||
|
{
|
||||||
|
destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample);
|
||||||
|
srcBuffers[channel] = buffer.getReadPointer (channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextPos = (bufferPos + 1) % bufferSize;
|
||||||
|
|
||||||
|
for (int m = info.numSamples; --m >= 0;)
|
||||||
|
{
|
||||||
|
jassert (sampsInBuffer > 0 && nextPos != endOfBufferPos);
|
||||||
|
|
||||||
|
const float alpha = (float) subSampleOffset;
|
||||||
|
|
||||||
|
for (int channel = 0; channel < channelsToProcess; ++channel)
|
||||||
|
*destBuffers[channel]++ = srcBuffers[channel][bufferPos]
|
||||||
|
+ alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]);
|
||||||
|
|
||||||
|
subSampleOffset += localRatio;
|
||||||
|
|
||||||
|
while (subSampleOffset >= 1.0)
|
||||||
|
{
|
||||||
|
if (++bufferPos >= bufferSize)
|
||||||
|
bufferPos = 0;
|
||||||
|
|
||||||
|
--sampsInBuffer;
|
||||||
|
|
||||||
|
nextPos = (bufferPos + 1) % bufferSize;
|
||||||
|
subSampleOffset -= 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localRatio < 0.9999)
|
||||||
|
{
|
||||||
|
// for up-sampling, apply the filter after transposing..
|
||||||
|
for (int i = channelsToProcess; --i >= 0;)
|
||||||
|
applyFilter (info.buffer->getWritePointer (i, info.startSample), info.numSamples, filterStates[i]);
|
||||||
|
}
|
||||||
|
else if (localRatio <= 1.0001 && info.numSamples > 0)
|
||||||
|
{
|
||||||
|
// if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities
|
||||||
|
for (int i = channelsToProcess; --i >= 0;)
|
||||||
|
{
|
||||||
|
const float* const endOfBuffer = info.buffer->getReadPointer (i, info.startSample + info.numSamples - 1);
|
||||||
|
FilterState& fs = filterStates[i];
|
||||||
|
|
||||||
|
if (info.numSamples > 1)
|
||||||
|
{
|
||||||
|
fs.y2 = fs.x2 = *(endOfBuffer - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fs.y2 = fs.y1;
|
||||||
|
fs.x2 = fs.x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.y1 = fs.x1 = *endOfBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jassert (sampsInBuffer >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::createLowPass (const double frequencyRatio)
|
||||||
|
{
|
||||||
|
const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio
|
||||||
|
: 0.5 * frequencyRatio;
|
||||||
|
|
||||||
|
const double n = 1.0 / std::tan (MathConstants<double>::pi * jmax (0.001, proportionalRate));
|
||||||
|
const double nSquared = n * n;
|
||||||
|
const double c1 = 1.0 / (1.0 + MathConstants<double>::sqrt2 * n + nSquared);
|
||||||
|
|
||||||
|
setFilterCoefficients (c1,
|
||||||
|
c1 * 2.0f,
|
||||||
|
c1,
|
||||||
|
1.0,
|
||||||
|
c1 * 2.0 * (1.0 - nSquared),
|
||||||
|
c1 * (1.0 - MathConstants<double>::sqrt2 * n + nSquared));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6)
|
||||||
|
{
|
||||||
|
const double a = 1.0 / c4;
|
||||||
|
|
||||||
|
c1 *= a;
|
||||||
|
c2 *= a;
|
||||||
|
c3 *= a;
|
||||||
|
c5 *= a;
|
||||||
|
c6 *= a;
|
||||||
|
|
||||||
|
coefficients[0] = c1;
|
||||||
|
coefficients[1] = c2;
|
||||||
|
coefficients[2] = c3;
|
||||||
|
coefficients[3] = c4;
|
||||||
|
coefficients[4] = c5;
|
||||||
|
coefficients[5] = c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::resetFilters()
|
||||||
|
{
|
||||||
|
if (filterStates != nullptr)
|
||||||
|
filterStates.clear ((size_t) numChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs)
|
||||||
|
{
|
||||||
|
while (--num >= 0)
|
||||||
|
{
|
||||||
|
const double in = *samples;
|
||||||
|
|
||||||
|
double out = coefficients[0] * in
|
||||||
|
+ coefficients[1] * fs.x1
|
||||||
|
+ coefficients[2] * fs.x2
|
||||||
|
- coefficients[4] * fs.y1
|
||||||
|
- coefficients[5] * fs.y2;
|
||||||
|
|
||||||
|
#if JUCE_INTEL
|
||||||
|
if (! (out < -1.0e-8 || out > 1.0e-8))
|
||||||
|
out = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fs.x2 = fs.x1;
|
||||||
|
fs.x1 = in;
|
||||||
|
fs.y2 = fs.y1;
|
||||||
|
fs.y1 = out;
|
||||||
|
|
||||||
|
*samples++ = (float) out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
105
modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h
Normal file
105
modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 type of AudioSource that takes an input source and changes its sample rate.
|
||||||
|
|
||||||
|
@see AudioSource, LagrangeInterpolator, CatmullRomInterpolator
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API ResamplingAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a ResamplingAudioSource for a given input source.
|
||||||
|
|
||||||
|
@param inputSource the input source to read from
|
||||||
|
@param deleteInputWhenDeleted if true, the input source will be deleted when
|
||||||
|
this object is deleted
|
||||||
|
@param numChannels the number of channels to process
|
||||||
|
*/
|
||||||
|
ResamplingAudioSource (AudioSource* inputSource,
|
||||||
|
bool deleteInputWhenDeleted,
|
||||||
|
int numChannels = 2);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~ResamplingAudioSource();
|
||||||
|
|
||||||
|
/** Changes the resampling ratio.
|
||||||
|
|
||||||
|
(This value can be changed at any time, even while the source is running).
|
||||||
|
|
||||||
|
@param samplesInPerOutputSample if set to 1.0, the input is passed through; higher
|
||||||
|
values will speed it up; lower values will slow it
|
||||||
|
down. The ratio must be greater than 0
|
||||||
|
*/
|
||||||
|
void setResamplingRatio (double samplesInPerOutputSample);
|
||||||
|
|
||||||
|
/** Returns the current resampling ratio.
|
||||||
|
|
||||||
|
This is the value that was set by setResamplingRatio().
|
||||||
|
*/
|
||||||
|
double getResamplingRatio() const noexcept { return ratio; }
|
||||||
|
|
||||||
|
/** Clears any buffers and filters that the resampler is using. */
|
||||||
|
void flushBuffers();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OptionalScopedPointer<AudioSource> input;
|
||||||
|
double ratio, lastRatio;
|
||||||
|
AudioBuffer<float> buffer;
|
||||||
|
int bufferPos, sampsInBuffer;
|
||||||
|
double subSampleOffset;
|
||||||
|
double coefficients[6];
|
||||||
|
SpinLock ratioLock;
|
||||||
|
const int numChannels;
|
||||||
|
HeapBlock<float*> destBuffers;
|
||||||
|
HeapBlock<const float*> srcBuffers;
|
||||||
|
|
||||||
|
void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6);
|
||||||
|
void createLowPass (double proportionalRate);
|
||||||
|
|
||||||
|
struct FilterState
|
||||||
|
{
|
||||||
|
double x1, x2, y1, y2;
|
||||||
|
};
|
||||||
|
|
||||||
|
HeapBlock<FilterState> filterStates;
|
||||||
|
void resetFilters();
|
||||||
|
|
||||||
|
void applyFilter (float* samples, int num, FilterState& fs);
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
83
modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp
Normal file
83
modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted)
|
||||||
|
: input (inputSource, deleteInputWhenDeleted),
|
||||||
|
bypass (false)
|
||||||
|
{
|
||||||
|
jassert (inputSource != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReverbAudioSource::~ReverbAudioSource() {}
|
||||||
|
|
||||||
|
void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||||
|
reverb.setSampleRate (sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbAudioSource::releaseResources() {}
|
||||||
|
|
||||||
|
void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
input->getNextAudioBlock (bufferToFill);
|
||||||
|
|
||||||
|
if (! bypass)
|
||||||
|
{
|
||||||
|
float* const firstChannel = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
|
||||||
|
|
||||||
|
if (bufferToFill.buffer->getNumChannels() > 1)
|
||||||
|
{
|
||||||
|
reverb.processStereo (firstChannel,
|
||||||
|
bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample),
|
||||||
|
bufferToFill.numSamples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reverb.processMono (firstChannel, bufferToFill.numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
reverb.setParameters (newParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbAudioSource::setBypassed (bool b) noexcept
|
||||||
|
{
|
||||||
|
if (bypass != b)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
bypass = b;
|
||||||
|
reverb.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
74
modules/juce_audio_basics/sources/juce_ReverbAudioSource.h
Normal file
74
modules/juce_audio_basics/sources/juce_ReverbAudioSource.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
An AudioSource that uses the Reverb class to apply a reverb to another AudioSource.
|
||||||
|
|
||||||
|
@see Reverb
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API ReverbAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Creates a ReverbAudioSource to process a given input source.
|
||||||
|
|
||||||
|
@param inputSource the input source to read from - this must not be null
|
||||||
|
@param deleteInputWhenDeleted if true, the input source will be deleted when
|
||||||
|
this object is deleted
|
||||||
|
*/
|
||||||
|
ReverbAudioSource (AudioSource* inputSource,
|
||||||
|
bool deleteInputWhenDeleted);
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~ReverbAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the parameters from the reverb. */
|
||||||
|
const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); }
|
||||||
|
|
||||||
|
/** Changes the reverb's parameters. */
|
||||||
|
void setParameters (const Reverb::Parameters& newParams);
|
||||||
|
|
||||||
|
void setBypassed (bool isBypassed) noexcept;
|
||||||
|
bool isBypassed() const noexcept { return bypass; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
CriticalSection lock;
|
||||||
|
OptionalScopedPointer<AudioSource> input;
|
||||||
|
Reverb reverb;
|
||||||
|
std::atomic<bool> bypass;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -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.
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
ToneGeneratorAudioSource::ToneGeneratorAudioSource()
|
||||||
|
: frequency (1000.0),
|
||||||
|
sampleRate (44100.0),
|
||||||
|
currentPhase (0.0),
|
||||||
|
phasePerSample (0.0),
|
||||||
|
amplitude (0.5f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ToneGeneratorAudioSource::~ToneGeneratorAudioSource()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude)
|
||||||
|
{
|
||||||
|
amplitude = newAmplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz)
|
||||||
|
{
|
||||||
|
frequency = newFrequencyHz;
|
||||||
|
phasePerSample = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double rate)
|
||||||
|
{
|
||||||
|
currentPhase = 0.0;
|
||||||
|
phasePerSample = 0.0;
|
||||||
|
sampleRate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToneGeneratorAudioSource::releaseResources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||||
|
{
|
||||||
|
if (phasePerSample == 0.0)
|
||||||
|
phasePerSample = MathConstants<double>::twoPi / (sampleRate / frequency);
|
||||||
|
|
||||||
|
for (int i = 0; i < info.numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float sample = amplitude * (float) std::sin (currentPhase);
|
||||||
|
currentPhase += phasePerSample;
|
||||||
|
|
||||||
|
for (int j = info.buffer->getNumChannels(); --j >= 0;)
|
||||||
|
info.buffer->setSample (j, info.startSample + i, sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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 simple AudioSource that generates a sine wave.
|
||||||
|
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API ToneGeneratorAudioSource : public AudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a ToneGeneratorAudioSource. */
|
||||||
|
ToneGeneratorAudioSource();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~ToneGeneratorAudioSource();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Sets the signal's amplitude. */
|
||||||
|
void setAmplitude (float newAmplitude);
|
||||||
|
|
||||||
|
/** Sets the signal's frequency. */
|
||||||
|
void setFrequency (double newFrequencyHz);
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
/** Implementation of the AudioSource method. */
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
double frequency, sampleRate;
|
||||||
|
double currentPhase, phasePerSample;
|
||||||
|
float amplitude;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
574
modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp
Normal file
574
modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp
Normal file
|
@ -0,0 +1,574 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
SynthesiserSound::SynthesiserSound() {}
|
||||||
|
SynthesiserSound::~SynthesiserSound() {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
SynthesiserVoice::SynthesiserVoice() {}
|
||||||
|
SynthesiserVoice::~SynthesiserVoice() {}
|
||||||
|
|
||||||
|
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const
|
||||||
|
{
|
||||||
|
return currentPlayingMidiChannel == midiChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate)
|
||||||
|
{
|
||||||
|
currentSampleRate = newRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthesiserVoice::isVoiceActive() const
|
||||||
|
{
|
||||||
|
return getCurrentlyPlayingNote() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthesiserVoice::clearCurrentNote()
|
||||||
|
{
|
||||||
|
currentlyPlayingNote = -1;
|
||||||
|
currentlyPlayingSound = nullptr;
|
||||||
|
currentPlayingMidiChannel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthesiserVoice::aftertouchChanged (int) {}
|
||||||
|
void SynthesiserVoice::channelPressureChanged (int) {}
|
||||||
|
|
||||||
|
bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept
|
||||||
|
{
|
||||||
|
return noteOnTime < other.noteOnTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer,
|
||||||
|
int startSample, int numSamples)
|
||||||
|
{
|
||||||
|
AudioBuffer<double> subBuffer (outputBuffer.getArrayOfWritePointers(),
|
||||||
|
outputBuffer.getNumChannels(),
|
||||||
|
startSample, numSamples);
|
||||||
|
|
||||||
|
tempBuffer.makeCopyOf (subBuffer, true);
|
||||||
|
renderNextBlock (tempBuffer, 0, numSamples);
|
||||||
|
subBuffer.makeCopyOf (tempBuffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
Synthesiser::Synthesiser()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i)
|
||||||
|
lastPitchWheelValues[i] = 0x2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Synthesiser::~Synthesiser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
SynthesiserVoice* Synthesiser::getVoice (const int index) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
return voices [index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::clearVoices()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
voices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
newVoice->setCurrentPlaybackSampleRate (sampleRate);
|
||||||
|
return voices.add (newVoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::removeVoice (const int index)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
voices.remove (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::clearSounds()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
sounds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
return sounds.add (newSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::removeSound (const int index)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
sounds.remove (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::setNoteStealingEnabled (const bool shouldSteal)
|
||||||
|
{
|
||||||
|
shouldStealNotes = shouldSteal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept
|
||||||
|
{
|
||||||
|
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
|
||||||
|
minimumSubBlockSize = numSamples;
|
||||||
|
subBlockSubdivisionIsStrict = shouldBeStrict;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void Synthesiser::setCurrentPlaybackSampleRate (const double newRate)
|
||||||
|
{
|
||||||
|
if (sampleRate != newRate)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
allNotesOff (0, false);
|
||||||
|
sampleRate = newRate;
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
voice->setCurrentPlaybackSampleRate (newRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename floatType>
|
||||||
|
void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio,
|
||||||
|
const MidiBuffer& midiData,
|
||||||
|
int startSample,
|
||||||
|
int numSamples)
|
||||||
|
{
|
||||||
|
// must set the sample rate before using this!
|
||||||
|
jassert (sampleRate != 0);
|
||||||
|
const int targetChannels = outputAudio.getNumChannels();
|
||||||
|
|
||||||
|
MidiBuffer::Iterator midiIterator (midiData);
|
||||||
|
midiIterator.setNextSamplePosition (startSample);
|
||||||
|
|
||||||
|
bool firstEvent = true;
|
||||||
|
int midiEventPos;
|
||||||
|
MidiMessage m;
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
while (numSamples > 0)
|
||||||
|
{
|
||||||
|
if (! midiIterator.getNextEvent (m, midiEventPos))
|
||||||
|
{
|
||||||
|
if (targetChannels > 0)
|
||||||
|
renderVoices (outputAudio, startSample, numSamples);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int samplesToNextMidiMessage = midiEventPos - startSample;
|
||||||
|
|
||||||
|
if (samplesToNextMidiMessage >= numSamples)
|
||||||
|
{
|
||||||
|
if (targetChannels > 0)
|
||||||
|
renderVoices (outputAudio, startSample, numSamples);
|
||||||
|
|
||||||
|
handleMidiEvent (m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize))
|
||||||
|
{
|
||||||
|
handleMidiEvent (m);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEvent = false;
|
||||||
|
|
||||||
|
if (targetChannels > 0)
|
||||||
|
renderVoices (outputAudio, startSample, samplesToNextMidiMessage);
|
||||||
|
|
||||||
|
handleMidiEvent (m);
|
||||||
|
startSample += samplesToNextMidiMessage;
|
||||||
|
numSamples -= samplesToNextMidiMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (midiIterator.getNextEvent (m, midiEventPos))
|
||||||
|
handleMidiEvent (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicit template instantiation
|
||||||
|
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
|
||||||
|
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
|
||||||
|
|
||||||
|
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples)
|
||||||
|
{
|
||||||
|
for (auto* voice : voices)
|
||||||
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples)
|
||||||
|
{
|
||||||
|
for (auto* voice : voices)
|
||||||
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleMidiEvent (const MidiMessage& m)
|
||||||
|
{
|
||||||
|
const int channel = m.getChannel();
|
||||||
|
|
||||||
|
if (m.isNoteOn())
|
||||||
|
{
|
||||||
|
noteOn (channel, m.getNoteNumber(), m.getFloatVelocity());
|
||||||
|
}
|
||||||
|
else if (m.isNoteOff())
|
||||||
|
{
|
||||||
|
noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true);
|
||||||
|
}
|
||||||
|
else if (m.isAllNotesOff() || m.isAllSoundOff())
|
||||||
|
{
|
||||||
|
allNotesOff (channel, true);
|
||||||
|
}
|
||||||
|
else if (m.isPitchWheel())
|
||||||
|
{
|
||||||
|
const int wheelPos = m.getPitchWheelValue();
|
||||||
|
lastPitchWheelValues [channel - 1] = wheelPos;
|
||||||
|
handlePitchWheel (channel, wheelPos);
|
||||||
|
}
|
||||||
|
else if (m.isAftertouch())
|
||||||
|
{
|
||||||
|
handleAftertouch (channel, m.getNoteNumber(), m.getAfterTouchValue());
|
||||||
|
}
|
||||||
|
else if (m.isChannelPressure())
|
||||||
|
{
|
||||||
|
handleChannelPressure (channel, m.getChannelPressureValue());
|
||||||
|
}
|
||||||
|
else if (m.isController())
|
||||||
|
{
|
||||||
|
handleController (channel, m.getControllerNumber(), m.getControllerValue());
|
||||||
|
}
|
||||||
|
else if (m.isProgramChange())
|
||||||
|
{
|
||||||
|
handleProgramChange (channel, m.getProgramChangeNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void Synthesiser::noteOn (const int midiChannel,
|
||||||
|
const int midiNoteNumber,
|
||||||
|
const float velocity)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* sound : sounds)
|
||||||
|
{
|
||||||
|
if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel))
|
||||||
|
{
|
||||||
|
// If hitting a note that's still ringing, stop it first (it could be
|
||||||
|
// still playing because of the sustain or sostenuto pedal).
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
|
||||||
|
stopVoice (voice, 1.0f, true);
|
||||||
|
|
||||||
|
startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes),
|
||||||
|
sound, midiChannel, midiNoteNumber, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::startVoice (SynthesiserVoice* const voice,
|
||||||
|
SynthesiserSound* const sound,
|
||||||
|
const int midiChannel,
|
||||||
|
const int midiNoteNumber,
|
||||||
|
const float velocity)
|
||||||
|
{
|
||||||
|
if (voice != nullptr && sound != nullptr)
|
||||||
|
{
|
||||||
|
if (voice->currentlyPlayingSound != nullptr)
|
||||||
|
voice->stopNote (0.0f, false);
|
||||||
|
|
||||||
|
voice->currentlyPlayingNote = midiNoteNumber;
|
||||||
|
voice->currentPlayingMidiChannel = midiChannel;
|
||||||
|
voice->noteOnTime = ++lastNoteOnCounter;
|
||||||
|
voice->currentlyPlayingSound = sound;
|
||||||
|
voice->setKeyDown (true);
|
||||||
|
voice->setSostenutoPedalDown (false);
|
||||||
|
voice->setSustainPedalDown (sustainPedalsDown[midiChannel]);
|
||||||
|
|
||||||
|
voice->startNote (midiNoteNumber, velocity, sound,
|
||||||
|
lastPitchWheelValues [midiChannel - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff)
|
||||||
|
{
|
||||||
|
jassert (voice != nullptr);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::noteOff (const int midiChannel,
|
||||||
|
const int midiNoteNumber,
|
||||||
|
const float velocity,
|
||||||
|
const bool allowTailOff)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
|
||||||
|
&& voice->isPlayingChannel (midiChannel))
|
||||||
|
{
|
||||||
|
if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound())
|
||||||
|
{
|
||||||
|
if (sound->appliesToNote (midiNoteNumber)
|
||||||
|
&& sound->appliesToChannel (midiChannel))
|
||||||
|
{
|
||||||
|
jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]);
|
||||||
|
|
||||||
|
voice->setKeyDown (false);
|
||||||
|
|
||||||
|
if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown()))
|
||||||
|
stopVoice (voice, velocity, allowTailOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
||||||
|
voice->stopNote (1.0f, allowTailOff);
|
||||||
|
|
||||||
|
sustainPedalsDown.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
||||||
|
voice->pitchWheelMoved (wheelValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleController (const int midiChannel,
|
||||||
|
const int controllerNumber,
|
||||||
|
const int controllerValue)
|
||||||
|
{
|
||||||
|
switch (controllerNumber)
|
||||||
|
{
|
||||||
|
case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break;
|
||||||
|
case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break;
|
||||||
|
case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
||||||
|
voice->controllerMoved (controllerNumber, controllerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
|
||||||
|
&& (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)))
|
||||||
|
voice->aftertouchChanged (aftertouchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue)
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
||||||
|
voice->channelPressureChanged (channelPressureValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown)
|
||||||
|
{
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (isDown)
|
||||||
|
{
|
||||||
|
sustainPedalsDown.setBit (midiChannel);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown())
|
||||||
|
voice->setSustainPedalDown (true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isPlayingChannel (midiChannel))
|
||||||
|
{
|
||||||
|
voice->setSustainPedalDown (false);
|
||||||
|
|
||||||
|
if (! (voice->isKeyDown() || voice->isSostenutoPedalDown()))
|
||||||
|
stopVoice (voice, 1.0f, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sustainPedalsDown.clearBit (midiChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown)
|
||||||
|
{
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->isPlayingChannel (midiChannel))
|
||||||
|
{
|
||||||
|
if (isDown)
|
||||||
|
voice->setSostenutoPedalDown (true);
|
||||||
|
else if (voice->isSostenutoPedalDown())
|
||||||
|
stopVoice (voice, 1.0f, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/)
|
||||||
|
{
|
||||||
|
ignoreUnused (midiChannel);
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synthesiser::handleProgramChange (int midiChannel, int programNumber)
|
||||||
|
{
|
||||||
|
ignoreUnused (midiChannel, programNumber);
|
||||||
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay,
|
||||||
|
int midiChannel, int midiNoteNumber,
|
||||||
|
const bool stealIfNoneAvailable) const
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay))
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
if (stealIfNoneAvailable)
|
||||||
|
return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay,
|
||||||
|
int /*midiChannel*/, int midiNoteNumber) const
|
||||||
|
{
|
||||||
|
// This voice-stealing algorithm applies the following heuristics:
|
||||||
|
// - Re-use the oldest notes first
|
||||||
|
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
|
||||||
|
|
||||||
|
// apparently you are trying to render audio without having any voices...
|
||||||
|
jassert (! voices.isEmpty());
|
||||||
|
|
||||||
|
// These are the voices we want to protect (ie: only steal if unavoidable)
|
||||||
|
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
|
||||||
|
SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
|
||||||
|
|
||||||
|
// this is a list of voices we can steal, sorted by how long they've been running
|
||||||
|
Array<SynthesiserVoice*> usableVoices;
|
||||||
|
usableVoices.ensureStorageAllocated (voices.size());
|
||||||
|
|
||||||
|
for (auto* voice : voices)
|
||||||
|
{
|
||||||
|
if (voice->canPlaySound (soundToPlay))
|
||||||
|
{
|
||||||
|
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise
|
||||||
|
|
||||||
|
usableVoices.add (voice);
|
||||||
|
|
||||||
|
// NB: Using a functor rather than a lambda here due to scare-stories about
|
||||||
|
// compilers generating code containing heap allocations..
|
||||||
|
struct Sorter
|
||||||
|
{
|
||||||
|
bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort (usableVoices.begin(), usableVoices.end(), Sorter());
|
||||||
|
|
||||||
|
if (! voice->isPlayingButReleased()) // Don't protect released notes
|
||||||
|
{
|
||||||
|
auto note = voice->getCurrentlyPlayingNote();
|
||||||
|
|
||||||
|
if (low == nullptr || note < low->getCurrentlyPlayingNote())
|
||||||
|
low = voice;
|
||||||
|
|
||||||
|
if (top == nullptr || note > top->getCurrentlyPlayingNote())
|
||||||
|
top = voice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
|
||||||
|
if (top == low)
|
||||||
|
top = nullptr;
|
||||||
|
|
||||||
|
// The oldest note that's playing with the target pitch is ideal..
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that has been released (no finger on it and not held by sustain pedal)
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top && voice->isPlayingButReleased())
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that doesn't have a finger on it:
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top && ! voice->isKeyDown())
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// Oldest voice that isn't protected
|
||||||
|
for (auto* voice : usableVoices)
|
||||||
|
if (voice != low && voice != top)
|
||||||
|
return voice;
|
||||||
|
|
||||||
|
// We've only got "protected" voices now: lowest note takes priority
|
||||||
|
jassert (low != nullptr);
|
||||||
|
|
||||||
|
// Duophonic synth: give priority to the bass note:
|
||||||
|
if (top != nullptr)
|
||||||
|
return top;
|
||||||
|
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
659
modules/juce_audio_basics/synthesisers/juce_Synthesiser.h
Normal file
659
modules/juce_audio_basics/synthesisers/juce_Synthesiser.h
Normal file
|
@ -0,0 +1,659 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Describes one of the sounds that a Synthesiser can play.
|
||||||
|
|
||||||
|
A synthesiser can contain one or more sounds, and a sound can choose which
|
||||||
|
midi notes and channels can trigger it.
|
||||||
|
|
||||||
|
The SynthesiserSound is a passive class that just describes what the sound is -
|
||||||
|
the actual audio rendering for a sound is done by a SynthesiserVoice. This allows
|
||||||
|
more than one SynthesiserVoice to play the same sound at the same time.
|
||||||
|
|
||||||
|
@see Synthesiser, SynthesiserVoice
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API SynthesiserSound : public ReferenceCountedObject
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
SynthesiserSound();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~SynthesiserSound();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns true if this sound should be played when a given midi note is pressed.
|
||||||
|
|
||||||
|
The Synthesiser will use this information when deciding which sounds to trigger
|
||||||
|
for a given note.
|
||||||
|
*/
|
||||||
|
virtual bool appliesToNote (int midiNoteNumber) = 0;
|
||||||
|
|
||||||
|
/** Returns true if the sound should be triggered by midi events on a given channel.
|
||||||
|
|
||||||
|
The Synthesiser will use this information when deciding which sounds to trigger
|
||||||
|
for a given note.
|
||||||
|
*/
|
||||||
|
virtual bool appliesToChannel (int midiChannel) = 0;
|
||||||
|
|
||||||
|
/** The class is reference-counted, so this is a handy pointer class for it. */
|
||||||
|
using Ptr = ReferenceCountedObjectPtr<SynthesiserSound>;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
JUCE_LEAK_DETECTOR (SynthesiserSound)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Represents a voice that a Synthesiser can use to play a SynthesiserSound.
|
||||||
|
|
||||||
|
A voice plays a single sound at a time, and a synthesiser holds an array of
|
||||||
|
voices so that it can play polyphonically.
|
||||||
|
|
||||||
|
@see Synthesiser, SynthesiserSound
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API SynthesiserVoice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a voice. */
|
||||||
|
SynthesiserVoice();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~SynthesiserVoice();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the midi note that this voice is currently playing.
|
||||||
|
Returns a value less than 0 if no note is playing.
|
||||||
|
*/
|
||||||
|
int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; }
|
||||||
|
|
||||||
|
/** Returns the sound that this voice is currently playing.
|
||||||
|
Returns nullptr if it's not playing.
|
||||||
|
*/
|
||||||
|
SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; }
|
||||||
|
|
||||||
|
/** Must return true if this voice object is capable of playing the given sound.
|
||||||
|
|
||||||
|
If there are different classes of sound, and different classes of voice, a voice can
|
||||||
|
choose which ones it wants to take on.
|
||||||
|
|
||||||
|
A typical implementation of this method may just return true if there's only one type
|
||||||
|
of voice and sound, or it might check the type of the sound object passed-in and
|
||||||
|
see if it's one that it understands.
|
||||||
|
*/
|
||||||
|
virtual bool canPlaySound (SynthesiserSound*) = 0;
|
||||||
|
|
||||||
|
/** Called to start a new note.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void startNote (int midiNoteNumber,
|
||||||
|
float velocity,
|
||||||
|
SynthesiserSound* sound,
|
||||||
|
int currentPitchWheelPosition) = 0;
|
||||||
|
|
||||||
|
/** Called to stop a note.
|
||||||
|
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
|
||||||
|
The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly.
|
||||||
|
|
||||||
|
If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all
|
||||||
|
sound immediately, and must call clearCurrentNote() to reset the state of this voice
|
||||||
|
and allow the synth to reassign it another sound.
|
||||||
|
|
||||||
|
If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to
|
||||||
|
begin fading out its sound, and it can stop playing until it's finished. As soon as it
|
||||||
|
finishes playing (during the rendering callback), it must make sure that it calls
|
||||||
|
clearCurrentNote().
|
||||||
|
*/
|
||||||
|
virtual void stopNote (float velocity, bool allowTailOff) = 0;
|
||||||
|
|
||||||
|
/** Returns true if this voice is currently busy playing a sound.
|
||||||
|
By default this just checks the getCurrentlyPlayingNote() value, but can
|
||||||
|
be overridden for more advanced checking.
|
||||||
|
*/
|
||||||
|
virtual bool isVoiceActive() const;
|
||||||
|
|
||||||
|
/** Called to let the voice know that the pitch wheel has been moved.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void pitchWheelMoved (int newPitchWheelValue) = 0;
|
||||||
|
|
||||||
|
/** Called to let the voice know that a midi controller has been moved.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0;
|
||||||
|
|
||||||
|
/** Called to let the voice know that the aftertouch has changed.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void aftertouchChanged (int newAftertouchValue);
|
||||||
|
|
||||||
|
/** Called to let the voice know that the channel pressure has changed.
|
||||||
|
This will be called during the rendering callback, so must be fast and thread-safe.
|
||||||
|
*/
|
||||||
|
virtual void channelPressureChanged (int newChannelPressureValue);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Renders the next block of data for this voice.
|
||||||
|
|
||||||
|
The output audio data must be added to the current contents of the buffer provided.
|
||||||
|
Only the region of the buffer between startSample and (startSample + numSamples)
|
||||||
|
should be altered by this method.
|
||||||
|
|
||||||
|
If the voice is currently silent, it should just return without doing anything.
|
||||||
|
|
||||||
|
If the sound that the voice is playing finishes during the course of this rendered
|
||||||
|
block, it must call clearCurrentNote(), to tell the synthesiser that it has finished.
|
||||||
|
|
||||||
|
The size of the blocks that are rendered can change each time it is called, and may
|
||||||
|
involve rendering as little as 1 sample at a time. In between rendering callbacks,
|
||||||
|
the voice's methods will be called to tell it about note and controller events.
|
||||||
|
*/
|
||||||
|
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
|
||||||
|
int startSample,
|
||||||
|
int numSamples) = 0;
|
||||||
|
|
||||||
|
/** A double-precision version of renderNextBlock() */
|
||||||
|
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer,
|
||||||
|
int startSample,
|
||||||
|
int numSamples);
|
||||||
|
|
||||||
|
/** Changes the voice's reference sample rate.
|
||||||
|
|
||||||
|
The rate is set so that subclasses know the output rate and can set their pitch
|
||||||
|
accordingly.
|
||||||
|
|
||||||
|
This method is called by the synth, and subclasses can access the current rate with
|
||||||
|
the currentSampleRate member.
|
||||||
|
*/
|
||||||
|
virtual void setCurrentPlaybackSampleRate (double newRate);
|
||||||
|
|
||||||
|
/** Returns true if the voice is currently playing a sound which is mapped to the given
|
||||||
|
midi channel.
|
||||||
|
|
||||||
|
If it's not currently playing, this will return false.
|
||||||
|
*/
|
||||||
|
virtual bool isPlayingChannel (int midiChannel) const;
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
double getSampleRate() const noexcept { return currentSampleRate; }
|
||||||
|
|
||||||
|
/** Returns true if the key that triggered this voice is still held down.
|
||||||
|
Note that the voice may still be playing after the key was released (e.g because the
|
||||||
|
sostenuto pedal is down).
|
||||||
|
*/
|
||||||
|
bool isKeyDown() const noexcept { return keyIsDown; }
|
||||||
|
|
||||||
|
/** Allows you to modify the flag indicating that the key that triggered this voice is still held down.
|
||||||
|
@see isKeyDown
|
||||||
|
*/
|
||||||
|
void setKeyDown (bool isNowDown) noexcept { keyIsDown = isNowDown; }
|
||||||
|
|
||||||
|
/** Returns true if the sustain pedal is currently active for this voice. */
|
||||||
|
bool isSustainPedalDown() const noexcept { return sustainPedalDown; }
|
||||||
|
|
||||||
|
/** Modifies the sustain pedal flag. */
|
||||||
|
void setSustainPedalDown (bool isNowDown) noexcept { sustainPedalDown = isNowDown; }
|
||||||
|
|
||||||
|
/** Returns true if the sostenuto pedal is currently active for this voice. */
|
||||||
|
bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; }
|
||||||
|
|
||||||
|
/** Modifies the sostenuto pedal flag. */
|
||||||
|
void setSostenutoPedalDown (bool isNowDown) noexcept { sostenutoPedalDown = isNowDown; }
|
||||||
|
|
||||||
|
/** Returns true if a voice is sounding in its release phase **/
|
||||||
|
bool isPlayingButReleased() const noexcept
|
||||||
|
{
|
||||||
|
return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if this voice started playing its current note before the other voice did. */
|
||||||
|
bool wasStartedBefore (const SynthesiserVoice& other) const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Resets the state of this voice after a sound has finished playing.
|
||||||
|
|
||||||
|
The subclass must call this when it finishes playing a note and becomes available
|
||||||
|
to play new ones.
|
||||||
|
|
||||||
|
It must either call it in the stopNote() method, or if the voice is tailing off,
|
||||||
|
then it should call it later during the renderNextBlock method, as soon as it
|
||||||
|
finishes its tail-off.
|
||||||
|
|
||||||
|
It can also be called at any time during the render callback if the sound happens
|
||||||
|
to have finished, e.g. if it's playing a sample and the sample finishes.
|
||||||
|
*/
|
||||||
|
void clearCurrentNote();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
friend class Synthesiser;
|
||||||
|
|
||||||
|
double currentSampleRate = 44100.0;
|
||||||
|
int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0;
|
||||||
|
uint32 noteOnTime = 0;
|
||||||
|
SynthesiserSound::Ptr currentlyPlayingSound;
|
||||||
|
bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false;
|
||||||
|
|
||||||
|
AudioBuffer<float> 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)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Base class for a musical device that can play sounds.
|
||||||
|
|
||||||
|
To create a synthesiser, you'll need to create a subclass of SynthesiserSound
|
||||||
|
to describe each sound available to your synth, and a subclass of SynthesiserVoice
|
||||||
|
which can play back one of these sounds.
|
||||||
|
|
||||||
|
Then you can use the addVoice() and addSound() methods to give the synthesiser a
|
||||||
|
set of sounds, and a set of voices it can use to play them. If you only give it
|
||||||
|
one voice it will be monophonic - the more voices it has, the more polyphony it'll
|
||||||
|
have available.
|
||||||
|
|
||||||
|
Then repeatedly call the renderNextBlock() method to produce the audio. Any midi
|
||||||
|
events that go in will be scanned for note on/off messages, and these are used to
|
||||||
|
start and stop the voices playing the appropriate sounds.
|
||||||
|
|
||||||
|
While it's playing, you can also cause notes to be triggered by calling the noteOn(),
|
||||||
|
noteOff() and other controller methods.
|
||||||
|
|
||||||
|
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it
|
||||||
|
what the target playback rate is. This value is passed on to the voices so that
|
||||||
|
they can pitch their output correctly.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API Synthesiser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a new synthesiser.
|
||||||
|
You'll need to add some sounds and voices before it'll make any sound.
|
||||||
|
*/
|
||||||
|
Synthesiser();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~Synthesiser();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Deletes all voices. */
|
||||||
|
void clearVoices();
|
||||||
|
|
||||||
|
/** Returns the number of voices that have been added. */
|
||||||
|
int getNumVoices() const noexcept { return voices.size(); }
|
||||||
|
|
||||||
|
/** Returns one of the voices that have been added. */
|
||||||
|
SynthesiserVoice* getVoice (int index) const;
|
||||||
|
|
||||||
|
/** Adds a new voice to the synth.
|
||||||
|
|
||||||
|
All the voices should be the same class of object and are treated equally.
|
||||||
|
|
||||||
|
The object passed in will be managed by the synthesiser, which will delete
|
||||||
|
it later on when no longer needed. The caller should not retain a pointer to the
|
||||||
|
voice.
|
||||||
|
*/
|
||||||
|
SynthesiserVoice* addVoice (SynthesiserVoice* newVoice);
|
||||||
|
|
||||||
|
/** Deletes one of the voices. */
|
||||||
|
void removeVoice (int index);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Deletes all sounds. */
|
||||||
|
void clearSounds();
|
||||||
|
|
||||||
|
/** Returns the number of sounds that have been added to the synth. */
|
||||||
|
int getNumSounds() const noexcept { return sounds.size(); }
|
||||||
|
|
||||||
|
/** Returns one of the sounds. */
|
||||||
|
SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; }
|
||||||
|
|
||||||
|
/** Adds a new sound to the synthesiser.
|
||||||
|
|
||||||
|
The object passed in is reference counted, so will be deleted when the
|
||||||
|
synthesiser and all voices are no longer using it.
|
||||||
|
*/
|
||||||
|
SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound);
|
||||||
|
|
||||||
|
/** Removes and deletes one of the sounds. */
|
||||||
|
void removeSound (int index);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** If set to true, then the synth will try to take over an existing voice if
|
||||||
|
it runs out and needs to play another note.
|
||||||
|
|
||||||
|
The value of this boolean is passed into findFreeVoice(), so the result will
|
||||||
|
depend on the implementation of this method.
|
||||||
|
*/
|
||||||
|
void setNoteStealingEnabled (bool shouldStealNotes);
|
||||||
|
|
||||||
|
/** Returns true if note-stealing is enabled.
|
||||||
|
@see setNoteStealingEnabled
|
||||||
|
*/
|
||||||
|
bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Triggers a note-on event.
|
||||||
|
|
||||||
|
The default method here will find all the sounds that want to be triggered by
|
||||||
|
this note/channel. For each sound, it'll try to find a free voice, and use the
|
||||||
|
voice to start playing the sound.
|
||||||
|
|
||||||
|
Subclasses might want to override this if they need a more complex algorithm.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
The midiChannel parameter is the channel, between 1 and 16 inclusive.
|
||||||
|
*/
|
||||||
|
virtual void noteOn (int midiChannel,
|
||||||
|
int midiNoteNumber,
|
||||||
|
float velocity);
|
||||||
|
|
||||||
|
/** Triggers a note-off event.
|
||||||
|
|
||||||
|
This will turn off any voices that are playing a sound for the given note/channel.
|
||||||
|
|
||||||
|
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
|
||||||
|
(if they can do). If this is false, the notes will all be cut off immediately.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
The midiChannel parameter is the channel, between 1 and 16 inclusive.
|
||||||
|
*/
|
||||||
|
virtual void noteOff (int midiChannel,
|
||||||
|
int midiNoteNumber,
|
||||||
|
float velocity,
|
||||||
|
bool allowTailOff);
|
||||||
|
|
||||||
|
/** Turns off all notes.
|
||||||
|
|
||||||
|
This will turn off any voices that are playing a sound on the given midi channel.
|
||||||
|
|
||||||
|
If midiChannel is 0 or less, then all voices will be turned off, regardless of
|
||||||
|
which channel they're playing. Otherwise it represents a valid midi channel, from
|
||||||
|
1 to 16 inclusive.
|
||||||
|
|
||||||
|
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
|
||||||
|
(if they can do). If this is false, the notes will all be cut off immediately.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
*/
|
||||||
|
virtual void allNotesOff (int midiChannel,
|
||||||
|
bool allowTailOff);
|
||||||
|
|
||||||
|
/** Sends a pitch-wheel message to any active voices.
|
||||||
|
|
||||||
|
This will send a pitch-wheel message to any voices that are playing sounds on
|
||||||
|
the given midi channel.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
@param midiChannel the midi channel, from 1 to 16 inclusive
|
||||||
|
@param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue()
|
||||||
|
*/
|
||||||
|
virtual void handlePitchWheel (int midiChannel,
|
||||||
|
int wheelValue);
|
||||||
|
|
||||||
|
/** Sends a midi controller message to any active voices.
|
||||||
|
|
||||||
|
This will send a midi controller message to any voices that are playing sounds on
|
||||||
|
the given midi channel.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
@param midiChannel the midi channel, from 1 to 16 inclusive
|
||||||
|
@param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber()
|
||||||
|
@param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue()
|
||||||
|
*/
|
||||||
|
virtual void handleController (int midiChannel,
|
||||||
|
int controllerNumber,
|
||||||
|
int controllerValue);
|
||||||
|
|
||||||
|
/** Sends an aftertouch message.
|
||||||
|
|
||||||
|
This will send an aftertouch message to any voices that are playing sounds on
|
||||||
|
the given midi channel and note number.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
@param midiChannel the midi channel, from 1 to 16 inclusive
|
||||||
|
@param midiNoteNumber the midi note number, 0 to 127
|
||||||
|
@param aftertouchValue the aftertouch value, between 0 and 127,
|
||||||
|
as returned by MidiMessage::getAftertouchValue()
|
||||||
|
*/
|
||||||
|
virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue);
|
||||||
|
|
||||||
|
/** Sends a channel pressure message.
|
||||||
|
|
||||||
|
This will send a channel pressure message to any voices that are playing sounds on
|
||||||
|
the given midi channel.
|
||||||
|
|
||||||
|
This method will be called automatically according to the midi data passed into
|
||||||
|
renderNextBlock(), but may be called explicitly too.
|
||||||
|
|
||||||
|
@param midiChannel the midi channel, from 1 to 16 inclusive
|
||||||
|
@param channelPressureValue the pressure value, between 0 and 127, as returned
|
||||||
|
by MidiMessage::getChannelPressureValue()
|
||||||
|
*/
|
||||||
|
virtual void handleChannelPressure (int midiChannel, int channelPressureValue);
|
||||||
|
|
||||||
|
/** Handles a sustain pedal event. */
|
||||||
|
virtual void handleSustainPedal (int midiChannel, bool isDown);
|
||||||
|
|
||||||
|
/** Handles a sostenuto pedal event. */
|
||||||
|
virtual void handleSostenutoPedal (int midiChannel, bool isDown);
|
||||||
|
|
||||||
|
/** Can be overridden to handle soft pedal events. */
|
||||||
|
virtual void handleSoftPedal (int midiChannel, bool isDown);
|
||||||
|
|
||||||
|
/** Can be overridden to handle an incoming program change message.
|
||||||
|
The base class implementation of this has no effect, but you may want to make your
|
||||||
|
own synth react to program changes.
|
||||||
|
*/
|
||||||
|
virtual void handleProgramChange (int midiChannel,
|
||||||
|
int programNumber);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Tells the synthesiser what the sample rate is for the audio it's being used to render.
|
||||||
|
|
||||||
|
This value is propagated to the voices so that they can use it to render the correct
|
||||||
|
pitches.
|
||||||
|
*/
|
||||||
|
virtual void setCurrentPlaybackSampleRate (double sampleRate);
|
||||||
|
|
||||||
|
/** Creates the next block of audio output.
|
||||||
|
|
||||||
|
This will process the next numSamples of data from all the voices, and add that output
|
||||||
|
to the audio block supplied, starting from the offset specified. Note that the
|
||||||
|
data will be added to the current contents of the buffer, so you should clear it
|
||||||
|
before calling this method if necessary.
|
||||||
|
|
||||||
|
The midi events in the inputMidi buffer are parsed for note and controller events,
|
||||||
|
and these are used to trigger the voices. Note that the startSample offset applies
|
||||||
|
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<float>& outputAudio,
|
||||||
|
const MidiBuffer& inputMidi,
|
||||||
|
int startSample,
|
||||||
|
int numSamples)
|
||||||
|
{
|
||||||
|
processNextBlock (outputAudio, inputMidi, startSample, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void renderNextBlock (AudioBuffer<double>& outputAudio,
|
||||||
|
const MidiBuffer& inputMidi,
|
||||||
|
int startSample,
|
||||||
|
int numSamples)
|
||||||
|
{
|
||||||
|
processNextBlock (outputAudio, inputMidi, startSample, 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.
|
||||||
|
*/
|
||||||
|
double getSampleRate() const noexcept { return sampleRate; }
|
||||||
|
|
||||||
|
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.
|
||||||
|
|
||||||
|
When rendering, the audio blocks that are passed into renderNextBlock() will be split up
|
||||||
|
into smaller blocks that lie between all the incoming midi messages, and it is these smaller
|
||||||
|
sub-blocks that are rendered with multiple calls to renderVoices().
|
||||||
|
|
||||||
|
Obviously in a pathological case where there are midi messages on every sample, then
|
||||||
|
renderVoices() could be called once per sample and lead to poor performance, so this
|
||||||
|
setting allows you to set a lower limit on the block size.
|
||||||
|
|
||||||
|
The default setting is 32, which means that midi messages are accurate to about < 1ms
|
||||||
|
accuracy, which is probably fine for most purposes, but you may want to increase or
|
||||||
|
decrease this value for your synth.
|
||||||
|
|
||||||
|
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples.
|
||||||
|
|
||||||
|
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed
|
||||||
|
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate
|
||||||
|
(this can sometimes help to avoid quantisation or phasing issues).
|
||||||
|
*/
|
||||||
|
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//==============================================================================
|
||||||
|
/** This is used to control access to the rendering callback and the note trigger methods. */
|
||||||
|
CriticalSection lock;
|
||||||
|
|
||||||
|
OwnedArray<SynthesiserVoice> voices;
|
||||||
|
ReferenceCountedArray<SynthesiserSound> sounds;
|
||||||
|
|
||||||
|
/** The last pitch-wheel values for each midi channel. */
|
||||||
|
int lastPitchWheelValues [16];
|
||||||
|
|
||||||
|
/** Renders the voices for the given range.
|
||||||
|
By default this just calls renderNextBlock() on each voice, but you may need
|
||||||
|
to override it to handle custom cases.
|
||||||
|
*/
|
||||||
|
virtual void renderVoices (AudioBuffer<float>& outputAudio,
|
||||||
|
int startSample, int numSamples);
|
||||||
|
virtual void renderVoices (AudioBuffer<double>& outputAudio,
|
||||||
|
int startSample, int numSamples);
|
||||||
|
|
||||||
|
/** Searches through the voices to find one that's not currently playing, and
|
||||||
|
which can play the given sound.
|
||||||
|
|
||||||
|
Returns nullptr if all voices are busy and stealing isn't enabled.
|
||||||
|
|
||||||
|
To implement a custom note-stealing algorithm, you can either override this
|
||||||
|
method, or (preferably) override findVoiceToSteal().
|
||||||
|
*/
|
||||||
|
virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay,
|
||||||
|
int midiChannel,
|
||||||
|
int midiNoteNumber,
|
||||||
|
bool stealIfNoneAvailable) const;
|
||||||
|
|
||||||
|
/** Chooses a voice that is most suitable for being re-used.
|
||||||
|
The default method will attempt to find the oldest voice that isn't the
|
||||||
|
bottom or top note being played. If that's not suitable for your synth,
|
||||||
|
you can override this method and do something more cunning instead.
|
||||||
|
*/
|
||||||
|
virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay,
|
||||||
|
int midiChannel,
|
||||||
|
int midiNoteNumber) const;
|
||||||
|
|
||||||
|
/** Starts a specified voice playing a particular sound.
|
||||||
|
You'll probably never need to call this, it's used internally by noteOn(), but
|
||||||
|
may be needed by subclasses for custom behaviours.
|
||||||
|
*/
|
||||||
|
void startVoice (SynthesiserVoice* voice,
|
||||||
|
SynthesiserSound* sound,
|
||||||
|
int midiChannel,
|
||||||
|
int midiNoteNumber,
|
||||||
|
float velocity);
|
||||||
|
|
||||||
|
/** Stops a given voice.
|
||||||
|
You should never need to call this, it's used internally by noteOff, but is protected
|
||||||
|
in case it's useful for some custom subclasses. It basically just calls through to
|
||||||
|
SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things.
|
||||||
|
*/
|
||||||
|
void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff);
|
||||||
|
|
||||||
|
/** Can be overridden to do custom handling of incoming midi events. */
|
||||||
|
virtual void handleMidiEvent (const MidiMessage&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
template <typename floatType>
|
||||||
|
void processNextBlock (AudioBuffer<floatType>& outputAudio,
|
||||||
|
const MidiBuffer& inputMidi,
|
||||||
|
int startSample,
|
||||||
|
int numSamples);
|
||||||
|
//==============================================================================
|
||||||
|
double sampleRate = 0;
|
||||||
|
uint32 lastNoteOnCounter = 0;
|
||||||
|
int minimumSubBlockSize = 32;
|
||||||
|
bool subBlockSubdivisionIsStrict = false;
|
||||||
|
bool shouldStealNotes = true;
|
||||||
|
BigInteger sustainPedalsDown;
|
||||||
|
|
||||||
|
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||||
|
// Note the new parameters for these methods.
|
||||||
|
virtual int findFreeVoice (const bool) const { return 0; }
|
||||||
|
virtual int noteOff (int, int, int) { return 0; }
|
||||||
|
virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; }
|
||||||
|
virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
1002
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
Normal file
1002
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
541
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h
Normal file
541
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h
Normal file
|
@ -0,0 +1,541 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Manages the state of some audio and midi i/o devices.
|
||||||
|
|
||||||
|
This class keeps tracks of a currently-selected audio device, through
|
||||||
|
with which it continuously streams data from an audio callback, as well as
|
||||||
|
one or more midi inputs.
|
||||||
|
|
||||||
|
The idea is that your application will create one global instance of this object,
|
||||||
|
and let it take care of creating and deleting specific types of audio devices
|
||||||
|
internally. So when the device is changed, your callbacks will just keep running
|
||||||
|
without having to worry about this.
|
||||||
|
|
||||||
|
The manager can save and reload all of its device settings as XML, which
|
||||||
|
makes it very easy for you to save and reload the audio setup of your
|
||||||
|
application.
|
||||||
|
|
||||||
|
And to make it easy to let the user change its settings, there's a component
|
||||||
|
to do just that - the AudioDeviceSelectorComponent class, which contains a set of
|
||||||
|
device selection/sample-rate/latency controls.
|
||||||
|
|
||||||
|
To use an AudioDeviceManager, create one, and use initialise() to set it up. Then
|
||||||
|
call addAudioCallback() to register your audio callback with it, and use that to process
|
||||||
|
your audio data.
|
||||||
|
|
||||||
|
The manager also acts as a handy hub for incoming midi messages, allowing a
|
||||||
|
listener to register for messages from either a specific midi device, or from whatever
|
||||||
|
the current default midi input device is. The listener then doesn't have to worry about
|
||||||
|
re-registering with different midi devices if they are changed or deleted.
|
||||||
|
|
||||||
|
And yet another neat trick is that amount of CPU time being used is measured and
|
||||||
|
available with the getCpuUsage() method.
|
||||||
|
|
||||||
|
The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to
|
||||||
|
listeners whenever one of its settings is changed.
|
||||||
|
|
||||||
|
@see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioDeviceManager : public ChangeBroadcaster
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a default AudioDeviceManager.
|
||||||
|
|
||||||
|
Initially no audio device will be selected. You should call the initialise() method
|
||||||
|
and register an audio callback with setAudioCallback() before it'll be able to
|
||||||
|
actually make any noise.
|
||||||
|
*/
|
||||||
|
AudioDeviceManager();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~AudioDeviceManager();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
This structure holds a set of properties describing the current audio setup.
|
||||||
|
|
||||||
|
An AudioDeviceManager uses this class to save/load its current settings, and to
|
||||||
|
specify your preferred options when opening a device.
|
||||||
|
|
||||||
|
@see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise()
|
||||||
|
*/
|
||||||
|
struct JUCE_API AudioDeviceSetup
|
||||||
|
{
|
||||||
|
/** Creates an AudioDeviceSetup object.
|
||||||
|
|
||||||
|
The default constructor sets all the member variables to indicate default values.
|
||||||
|
You can then fill-in any values you want to before passing the object to
|
||||||
|
AudioDeviceManager::initialise().
|
||||||
|
*/
|
||||||
|
AudioDeviceSetup();
|
||||||
|
|
||||||
|
bool operator== (const AudioDeviceSetup& other) const;
|
||||||
|
bool operator!= (const AudioDeviceSetup& other) const;
|
||||||
|
|
||||||
|
/** The name of the audio device used for output.
|
||||||
|
The name has to be one of the ones listed by the AudioDeviceManager's currently
|
||||||
|
selected device type.
|
||||||
|
This may be the same as the input device.
|
||||||
|
An empty string indicates the default device.
|
||||||
|
*/
|
||||||
|
String outputDeviceName;
|
||||||
|
|
||||||
|
/** The name of the audio device used for input.
|
||||||
|
This may be the same as the output device.
|
||||||
|
An empty string indicates the default device.
|
||||||
|
*/
|
||||||
|
String inputDeviceName;
|
||||||
|
|
||||||
|
/** The current sample rate.
|
||||||
|
This rate is used for both the input and output devices.
|
||||||
|
A value of 0 indicates that you don't care what rate is used, and the
|
||||||
|
device will choose a sensible rate for you.
|
||||||
|
*/
|
||||||
|
double sampleRate;
|
||||||
|
|
||||||
|
/** The buffer size, in samples.
|
||||||
|
This buffer size is used for both the input and output devices.
|
||||||
|
A value of 0 indicates the default buffer size.
|
||||||
|
*/
|
||||||
|
int bufferSize;
|
||||||
|
|
||||||
|
/** The set of active input channels.
|
||||||
|
The bits that are set in this array indicate the channels of the
|
||||||
|
input device that are active.
|
||||||
|
If useDefaultInputChannels is true, this value is ignored.
|
||||||
|
*/
|
||||||
|
BigInteger inputChannels;
|
||||||
|
|
||||||
|
/** If this is true, it indicates that the inputChannels array
|
||||||
|
should be ignored, and instead, the device's default channels
|
||||||
|
should be used.
|
||||||
|
*/
|
||||||
|
bool useDefaultInputChannels;
|
||||||
|
|
||||||
|
/** The set of active output channels.
|
||||||
|
The bits that are set in this array indicate the channels of the
|
||||||
|
input device that are active.
|
||||||
|
If useDefaultOutputChannels is true, this value is ignored.
|
||||||
|
*/
|
||||||
|
BigInteger outputChannels;
|
||||||
|
|
||||||
|
/** If this is true, it indicates that the outputChannels array
|
||||||
|
should be ignored, and instead, the device's default channels
|
||||||
|
should be used.
|
||||||
|
*/
|
||||||
|
bool useDefaultOutputChannels;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Opens a set of audio devices ready for use.
|
||||||
|
|
||||||
|
This will attempt to open either a default audio device, or one that was
|
||||||
|
previously saved as XML.
|
||||||
|
|
||||||
|
@param numInputChannelsNeeded the maximum number of input channels your app would like to
|
||||||
|
use (the actual number of channels opened may be less than
|
||||||
|
the number requested)
|
||||||
|
@param numOutputChannelsNeeded the maximum number of output channels your app would like to
|
||||||
|
use (the actual number of channels opened may be less than
|
||||||
|
the number requested)
|
||||||
|
@param savedState either a previously-saved state that was produced
|
||||||
|
by createStateXml(), or nullptr if you want the manager
|
||||||
|
to choose the best device to open.
|
||||||
|
@param selectDefaultDeviceOnFailure if true, then if the device specified in the XML
|
||||||
|
fails to open, then a default device will be used
|
||||||
|
instead. If false, then on failure, no device is
|
||||||
|
opened.
|
||||||
|
@param preferredDefaultDeviceName if this is not empty, and there's a device with this
|
||||||
|
name, then that will be used as the default device
|
||||||
|
(assuming that there wasn't one specified in the XML).
|
||||||
|
The string can actually be a simple wildcard, containing "*"
|
||||||
|
and "?" characters
|
||||||
|
@param preferredSetupOptions if this is non-null, the structure will be used as the
|
||||||
|
set of preferred settings when opening the device. If you
|
||||||
|
use this parameter, the preferredDefaultDeviceName
|
||||||
|
field will be ignored
|
||||||
|
|
||||||
|
@returns an error message if anything went wrong, or an empty string if it worked ok.
|
||||||
|
*/
|
||||||
|
String initialise (int numInputChannelsNeeded,
|
||||||
|
int numOutputChannelsNeeded,
|
||||||
|
const XmlElement* savedState,
|
||||||
|
bool selectDefaultDeviceOnFailure,
|
||||||
|
const String& preferredDefaultDeviceName = String(),
|
||||||
|
const AudioDeviceSetup* preferredSetupOptions = nullptr);
|
||||||
|
|
||||||
|
/** Resets everything to a default device setup, clearing any stored settings. */
|
||||||
|
String initialiseWithDefaultDevices (int numInputChannelsNeeded,
|
||||||
|
int numOutputChannelsNeeded);
|
||||||
|
|
||||||
|
/** Returns some XML representing the current state of the manager.
|
||||||
|
|
||||||
|
This stores the current device, its samplerate, block size, etc, and
|
||||||
|
can be restored later with initialise().
|
||||||
|
|
||||||
|
Note that this can return a null pointer if no settings have been explicitly changed
|
||||||
|
(i.e. if the device manager has just been left in its default state).
|
||||||
|
*/
|
||||||
|
XmlElement* createStateXml() const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the current device properties that are in use.
|
||||||
|
@see setAudioDeviceSetup
|
||||||
|
*/
|
||||||
|
void getAudioDeviceSetup (AudioDeviceSetup& result) const;
|
||||||
|
|
||||||
|
/** Changes the current device or its settings.
|
||||||
|
|
||||||
|
If you want to change a device property, like the current sample rate or
|
||||||
|
block size, you can call getAudioDeviceSetup() to retrieve the current
|
||||||
|
settings, then tweak the appropriate fields in the AudioDeviceSetup structure,
|
||||||
|
and pass it back into this method to apply the new settings.
|
||||||
|
|
||||||
|
@param newSetup the settings that you'd like to use
|
||||||
|
@param treatAsChosenDevice if this is true and if the device opens correctly, these new
|
||||||
|
settings will be taken as having been explicitly chosen by the
|
||||||
|
user, and the next time createStateXml() is called, these settings
|
||||||
|
will be returned. If it's false, then the device is treated as a
|
||||||
|
temporary or default device, and a call to createStateXml() will
|
||||||
|
return either the last settings that were made with treatAsChosenDevice
|
||||||
|
as true, or the last XML settings that were passed into initialise().
|
||||||
|
@returns an error message if anything went wrong, or an empty string if it worked ok.
|
||||||
|
|
||||||
|
@see getAudioDeviceSetup
|
||||||
|
*/
|
||||||
|
String setAudioDeviceSetup (const AudioDeviceSetup& newSetup,
|
||||||
|
bool treatAsChosenDevice);
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns the currently-active audio device. */
|
||||||
|
AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice.get(); }
|
||||||
|
|
||||||
|
/** Returns the type of audio device currently in use.
|
||||||
|
@see setCurrentAudioDeviceType
|
||||||
|
*/
|
||||||
|
String getCurrentAudioDeviceType() const { return currentDeviceType; }
|
||||||
|
|
||||||
|
/** Returns the currently active audio device type object.
|
||||||
|
Don't keep a copy of this pointer - it's owned by the device manager and could
|
||||||
|
change at any time.
|
||||||
|
*/
|
||||||
|
AudioIODeviceType* getCurrentDeviceTypeObject() const;
|
||||||
|
|
||||||
|
/** Changes the class of audio device being used.
|
||||||
|
|
||||||
|
This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call
|
||||||
|
this because there's only one type: CoreAudio.
|
||||||
|
|
||||||
|
For a list of types, see getAvailableDeviceTypes().
|
||||||
|
*/
|
||||||
|
void setCurrentAudioDeviceType (const String& type,
|
||||||
|
bool treatAsChosenDevice);
|
||||||
|
|
||||||
|
/** Closes the currently-open device.
|
||||||
|
You can call restartLastAudioDevice() later to reopen it in the same state
|
||||||
|
that it was just in.
|
||||||
|
*/
|
||||||
|
void closeAudioDevice();
|
||||||
|
|
||||||
|
/** Tries to reload the last audio device that was running.
|
||||||
|
|
||||||
|
Note that this only reloads the last device that was running before
|
||||||
|
closeAudioDevice() was called - it doesn't reload any kind of saved-state,
|
||||||
|
and can only be called after a device has been opened with SetAudioDevice().
|
||||||
|
|
||||||
|
If a device is already open, this call will do nothing.
|
||||||
|
*/
|
||||||
|
void restartLastAudioDevice();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Registers an audio callback to be used.
|
||||||
|
|
||||||
|
The manager will redirect callbacks from whatever audio device is currently
|
||||||
|
in use to all registered callback objects. If more than one callback is
|
||||||
|
active, they will all be given the same input data, and their outputs will
|
||||||
|
be summed.
|
||||||
|
|
||||||
|
If necessary, this method will invoke audioDeviceAboutToStart() on the callback
|
||||||
|
object before returning.
|
||||||
|
|
||||||
|
To remove a callback, use removeAudioCallback().
|
||||||
|
*/
|
||||||
|
void addAudioCallback (AudioIODeviceCallback* newCallback);
|
||||||
|
|
||||||
|
/** Deregisters a previously added callback.
|
||||||
|
|
||||||
|
If necessary, this method will invoke audioDeviceStopped() on the callback
|
||||||
|
object before returning.
|
||||||
|
|
||||||
|
@see addAudioCallback
|
||||||
|
*/
|
||||||
|
void removeAudioCallback (AudioIODeviceCallback* callback);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the average proportion of available CPU being spent inside the audio callbacks.
|
||||||
|
@returns A value between 0 and 1.0 to indicate the approximate proportion of CPU
|
||||||
|
time spent in the callbacks.
|
||||||
|
*/
|
||||||
|
double getCpuUsage() const;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Enables or disables a midi input device.
|
||||||
|
|
||||||
|
The list of devices can be obtained with the MidiInput::getDevices() method.
|
||||||
|
|
||||||
|
Any incoming messages from enabled input devices will be forwarded on to all the
|
||||||
|
listeners that have been registered with the addMidiInputCallback() method. They
|
||||||
|
can either register for messages from a particular device, or from just the
|
||||||
|
"default" midi input.
|
||||||
|
|
||||||
|
Routing the midi input via an AudioDeviceManager means that when a listener
|
||||||
|
registers for the default midi input, this default device can be changed by the
|
||||||
|
manager without the listeners having to know about it or re-register.
|
||||||
|
|
||||||
|
It also means that a listener can stay registered for a midi input that is disabled
|
||||||
|
or not present, so that when the input is re-enabled, the listener will start
|
||||||
|
receiving messages again.
|
||||||
|
|
||||||
|
@see addMidiInputCallback, isMidiInputEnabled
|
||||||
|
*/
|
||||||
|
void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled);
|
||||||
|
|
||||||
|
/** Returns true if a given midi input device is being used.
|
||||||
|
@see setMidiInputEnabled
|
||||||
|
*/
|
||||||
|
bool isMidiInputEnabled (const String& midiInputDeviceName) const;
|
||||||
|
|
||||||
|
/** Registers a listener for callbacks when midi events arrive from a midi input.
|
||||||
|
|
||||||
|
The device name can be empty to indicate that it wants to receive all incoming
|
||||||
|
events from all the enabled MIDI inputs. Or it can be the name of one of the
|
||||||
|
MIDI input devices if it just wants the events from that device. (see
|
||||||
|
MidiInput::getDevices() for the list of device names).
|
||||||
|
|
||||||
|
Only devices which are enabled (see the setMidiInputEnabled() method) will have their
|
||||||
|
events forwarded on to listeners.
|
||||||
|
*/
|
||||||
|
void addMidiInputCallback (const String& midiInputDeviceName,
|
||||||
|
MidiInputCallback* callback);
|
||||||
|
|
||||||
|
/** Removes a listener that was previously registered with addMidiInputCallback(). */
|
||||||
|
void removeMidiInputCallback (const String& midiInputDeviceName,
|
||||||
|
MidiInputCallback* callback);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Sets a midi output device to use as the default.
|
||||||
|
|
||||||
|
The list of devices can be obtained with the MidiOutput::getDevices() method.
|
||||||
|
|
||||||
|
The specified device will be opened automatically and can be retrieved with the
|
||||||
|
getDefaultMidiOutput() method.
|
||||||
|
|
||||||
|
Pass in an empty string to deselect all devices. For the default device, you
|
||||||
|
can use MidiOutput::getDevices() [MidiOutput::getDefaultDeviceIndex()].
|
||||||
|
|
||||||
|
@see getDefaultMidiOutput, getDefaultMidiOutputName
|
||||||
|
*/
|
||||||
|
void setDefaultMidiOutput (const String& deviceName);
|
||||||
|
|
||||||
|
/** Returns the name of the default midi output.
|
||||||
|
@see setDefaultMidiOutput, getDefaultMidiOutput
|
||||||
|
*/
|
||||||
|
const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputName; }
|
||||||
|
|
||||||
|
/** Returns the current default midi output device.
|
||||||
|
If no device has been selected, or the device can't be opened, this will return nullptr.
|
||||||
|
@see getDefaultMidiOutputName
|
||||||
|
*/
|
||||||
|
MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput.get(); }
|
||||||
|
|
||||||
|
/** Returns a list of the types of device supported. */
|
||||||
|
const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a list of available types.
|
||||||
|
|
||||||
|
This will add a set of new AudioIODeviceType objects to the specified list, to
|
||||||
|
represent each available types of device.
|
||||||
|
|
||||||
|
You can override this if your app needs to do something specific, like avoid
|
||||||
|
using DirectSound devices, etc.
|
||||||
|
*/
|
||||||
|
virtual void createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& types);
|
||||||
|
|
||||||
|
/** Adds a new device type to the list of types.
|
||||||
|
The manager will take ownership of the object that is passed-in.
|
||||||
|
*/
|
||||||
|
void addAudioDeviceType (AudioIODeviceType* newDeviceType);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Plays a beep through the current audio device.
|
||||||
|
|
||||||
|
This is here to allow the audio setup UI panels to easily include a "test"
|
||||||
|
button so that the user can check where the audio is coming from.
|
||||||
|
*/
|
||||||
|
void playTestSound();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A simple reference-counted struct that holds a level-meter value that can be read
|
||||||
|
using getCurrentLevel().
|
||||||
|
|
||||||
|
This is used to ensure that the level processing code is only executed when something
|
||||||
|
holds a reference to one of these objects and will be bypassed otherwise.
|
||||||
|
|
||||||
|
@see getInputLevelGetter, getOutputLevelGetter
|
||||||
|
*/
|
||||||
|
struct LevelMeter : public ReferenceCountedObject
|
||||||
|
{
|
||||||
|
LevelMeter() noexcept;
|
||||||
|
double getCurrentLevel() const noexcept;
|
||||||
|
|
||||||
|
using Ptr = ReferenceCountedObjectPtr<LevelMeter>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AudioDeviceManager;
|
||||||
|
|
||||||
|
Atomic<float> level { 0 };
|
||||||
|
void updateLevel (const float* const*, int numChannels, int numSamples) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Returns a reference-counted object that can be used to get the current input level.
|
||||||
|
|
||||||
|
You need to store this object locally to ensure that the reference count is incremented
|
||||||
|
and decremented properly. The current input level value can be read using getCurrentLevel().
|
||||||
|
*/
|
||||||
|
LevelMeter::Ptr getInputLevelGetter() noexcept { return inputLevelGetter; }
|
||||||
|
|
||||||
|
/** Returns a reference-counted object that can be used to get the current input level.
|
||||||
|
|
||||||
|
You need to store this object locally to ensure that the reference count is incremented
|
||||||
|
and decremented properly. The current input level value can be read using getCurrentLevel().
|
||||||
|
*/
|
||||||
|
LevelMeter::Ptr getOutputLevelGetter() noexcept { return outputLevelGetter; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the a lock that can be used to synchronise access to the audio callback.
|
||||||
|
Obviously while this is locked, you're blocking the audio thread from running, so
|
||||||
|
it must only be used for very brief periods when absolutely necessary.
|
||||||
|
*/
|
||||||
|
CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; }
|
||||||
|
|
||||||
|
/** Returns the a lock that can be used to synchronise access to the midi callback.
|
||||||
|
Obviously while this is locked, you're blocking the midi system from running, so
|
||||||
|
it must only be used for very brief periods when absolutely necessary.
|
||||||
|
*/
|
||||||
|
CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the number of under- or over runs reported.
|
||||||
|
|
||||||
|
This method will use the underlying device's native getXRunCount if it supports
|
||||||
|
it. Otherwise it will estimate the number of under-/overruns by measuring the
|
||||||
|
time it spent in the audio callback.
|
||||||
|
*/
|
||||||
|
int getXRunCount() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OwnedArray<AudioIODeviceType> availableDeviceTypes;
|
||||||
|
OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs;
|
||||||
|
|
||||||
|
AudioDeviceSetup currentSetup;
|
||||||
|
std::unique_ptr<AudioIODevice> currentAudioDevice;
|
||||||
|
Array<AudioIODeviceCallback*> callbacks;
|
||||||
|
int numInputChansNeeded = 0, numOutputChansNeeded = 2;
|
||||||
|
String currentDeviceType;
|
||||||
|
BigInteger inputChannels, outputChannels;
|
||||||
|
std::unique_ptr<XmlElement> lastExplicitSettings;
|
||||||
|
mutable bool listNeedsScanning = true;
|
||||||
|
AudioBuffer<float> tempBuffer;
|
||||||
|
|
||||||
|
struct MidiCallbackInfo
|
||||||
|
{
|
||||||
|
String deviceName;
|
||||||
|
MidiInputCallback* callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
StringArray midiInsFromXml;
|
||||||
|
OwnedArray<MidiInput> enabledMidiInputs;
|
||||||
|
Array<MidiCallbackInfo> midiCallbacks;
|
||||||
|
|
||||||
|
String defaultMidiOutputName;
|
||||||
|
std::unique_ptr<MidiOutput> defaultMidiOutput;
|
||||||
|
CriticalSection audioCallbackLock, midiCallbackLock;
|
||||||
|
|
||||||
|
std::unique_ptr<AudioBuffer<float>> testSound;
|
||||||
|
int testSoundPosition = 0;
|
||||||
|
|
||||||
|
double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0;
|
||||||
|
int xruns = 0;
|
||||||
|
|
||||||
|
LevelMeter::Ptr inputLevelGetter { new LevelMeter() },
|
||||||
|
outputLevelGetter { new LevelMeter() };
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class CallbackHandler;
|
||||||
|
friend class CallbackHandler;
|
||||||
|
friend struct ContainerDeletePolicy<CallbackHandler>;
|
||||||
|
std::unique_ptr<CallbackHandler> callbackHandler;
|
||||||
|
|
||||||
|
void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels,
|
||||||
|
float** outputChannelData, int totalNumOutputChannels, int numSamples);
|
||||||
|
void audioDeviceAboutToStartInt (AudioIODevice*);
|
||||||
|
void audioDeviceStoppedInt();
|
||||||
|
void audioDeviceErrorInt (const String&);
|
||||||
|
void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&);
|
||||||
|
void audioDeviceListChanged();
|
||||||
|
|
||||||
|
String restartDevice (int blockSizeToUse, double sampleRateToUse,
|
||||||
|
const BigInteger& ins, const BigInteger& outs);
|
||||||
|
void stopDevice();
|
||||||
|
|
||||||
|
void updateXml();
|
||||||
|
|
||||||
|
void createDeviceTypesIfNeeded();
|
||||||
|
void scanDevicesIfNeeded();
|
||||||
|
void deleteCurrentDevice();
|
||||||
|
double chooseBestSampleRate (double preferred) const;
|
||||||
|
int chooseBestBufferSize (int preferred) const;
|
||||||
|
void insertDefaultDeviceNames (AudioDeviceSetup&) const;
|
||||||
|
String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
|
||||||
|
String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure,
|
||||||
|
const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
|
||||||
|
|
||||||
|
AudioIODeviceType* findType (const String& inputName, const String& outputName);
|
||||||
|
AudioIODeviceType* findType (const String& typeName);
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
45
modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp
Normal file
45
modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName)
|
||||||
|
: name (deviceName), typeName (deviceTypeName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioIODevice::~AudioIODevice() {}
|
||||||
|
|
||||||
|
void AudioIODeviceCallback::audioDeviceError (const String&) {}
|
||||||
|
bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; }
|
||||||
|
bool AudioIODevice::hasControlPanel() const { return false; }
|
||||||
|
int AudioIODevice::getXRunCount() const noexcept { return -1; }
|
||||||
|
|
||||||
|
bool AudioIODevice::showControlPanel()
|
||||||
|
{
|
||||||
|
jassertfalse; // this should only be called for devices which return true from
|
||||||
|
// their hasControlPanel() method.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
325
modules/juce_audio_devices/audio_io/juce_AudioIODevice.h
Normal file
325
modules/juce_audio_devices/audio_io/juce_AudioIODevice.h
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
class AudioIODevice;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
One of these is passed to an AudioIODevice object to stream the audio data
|
||||||
|
in and out.
|
||||||
|
|
||||||
|
The AudioIODevice will repeatedly call this class's audioDeviceIOCallback()
|
||||||
|
method on its own high-priority audio thread, when it needs to send or receive
|
||||||
|
the next block of data.
|
||||||
|
|
||||||
|
@see AudioIODevice, AudioDeviceManager
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioIODeviceCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~AudioIODeviceCallback() {}
|
||||||
|
|
||||||
|
/** Processes a block of incoming and outgoing audio data.
|
||||||
|
|
||||||
|
The subclass's implementation should use the incoming audio for whatever
|
||||||
|
purposes it needs to, and must fill all the output channels with the next
|
||||||
|
block of output data before returning.
|
||||||
|
|
||||||
|
The channel data is arranged with the same array indices as the channel name
|
||||||
|
array returned by AudioIODevice::getOutputChannelNames(), but those channels
|
||||||
|
that aren't specified in AudioIODevice::open() will have a null pointer for their
|
||||||
|
associated channel, so remember to check for this.
|
||||||
|
|
||||||
|
@param inputChannelData a set of arrays containing the audio data for each
|
||||||
|
incoming channel - this data is valid until the function
|
||||||
|
returns. There will be one channel of data for each input
|
||||||
|
channel that was enabled when the audio device was opened
|
||||||
|
(see AudioIODevice::open())
|
||||||
|
@param numInputChannels the number of pointers to channel data in the
|
||||||
|
inputChannelData array.
|
||||||
|
@param outputChannelData a set of arrays which need to be filled with the data
|
||||||
|
that should be sent to each outgoing channel of the device.
|
||||||
|
There will be one channel of data for each output channel
|
||||||
|
that was enabled when the audio device was opened (see
|
||||||
|
AudioIODevice::open())
|
||||||
|
The initial contents of the array is undefined, so the
|
||||||
|
callback function must fill all the channels with zeros if
|
||||||
|
its output is silence. Failing to do this could cause quite
|
||||||
|
an unpleasant noise!
|
||||||
|
@param numOutputChannels the number of pointers to channel data in the
|
||||||
|
outputChannelData array.
|
||||||
|
@param numSamples the number of samples in each channel of the input and
|
||||||
|
output arrays. The number of samples will depend on the
|
||||||
|
audio device's buffer size and will usually remain constant,
|
||||||
|
although this isn't guaranteed. For example, on Android,
|
||||||
|
on devices which support it, Android will chop up your audio
|
||||||
|
processing into several smaller callbacks to ensure higher audio
|
||||||
|
performance. So make sure your code can cope with reasonable
|
||||||
|
changes in the buffer size from one callback to the next.
|
||||||
|
*/
|
||||||
|
virtual void audioDeviceIOCallback (const float** inputChannelData,
|
||||||
|
int numInputChannels,
|
||||||
|
float** outputChannelData,
|
||||||
|
int numOutputChannels,
|
||||||
|
int numSamples) = 0;
|
||||||
|
|
||||||
|
/** Called to indicate that the device is about to start calling back.
|
||||||
|
|
||||||
|
This will be called just before the audio callbacks begin, either when this
|
||||||
|
callback has just been added to an audio device, or after the device has been
|
||||||
|
restarted because of a sample-rate or block-size change.
|
||||||
|
|
||||||
|
You can use this opportunity to find out the sample rate and block size
|
||||||
|
that the device is going to use by calling the AudioIODevice::getCurrentSampleRate()
|
||||||
|
and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer.
|
||||||
|
|
||||||
|
@param device the audio IO device that will be used to drive the callback.
|
||||||
|
Note that if you're going to store this this pointer, it is
|
||||||
|
only valid until the next time that audioDeviceStopped is called.
|
||||||
|
*/
|
||||||
|
virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0;
|
||||||
|
|
||||||
|
/** Called to indicate that the device has stopped. */
|
||||||
|
virtual void audioDeviceStopped() = 0;
|
||||||
|
|
||||||
|
/** This can be overridden to be told if the device generates an error while operating.
|
||||||
|
Be aware that this could be called by any thread! And not all devices perform
|
||||||
|
this callback.
|
||||||
|
*/
|
||||||
|
virtual void audioDeviceError (const String& errorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Base class for an audio device with synchronised input and output channels.
|
||||||
|
|
||||||
|
Subclasses of this are used to implement different protocols such as DirectSound,
|
||||||
|
ASIO, CoreAudio, etc.
|
||||||
|
|
||||||
|
To create one of these, you'll need to use the AudioIODeviceType class - see the
|
||||||
|
documentation for that class for more info.
|
||||||
|
|
||||||
|
For an easier way of managing audio devices and their settings, have a look at the
|
||||||
|
AudioDeviceManager class.
|
||||||
|
|
||||||
|
@see AudioIODeviceType, AudioDeviceManager
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioIODevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~AudioIODevice();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the device's name, (as set in the constructor). */
|
||||||
|
const String& getName() const noexcept { return name; }
|
||||||
|
|
||||||
|
/** Returns the type of the device.
|
||||||
|
|
||||||
|
E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it.
|
||||||
|
*/
|
||||||
|
const String& getTypeName() const noexcept { return typeName; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the names of all the available output channels on this device.
|
||||||
|
To find out which of these are currently in use, call getActiveOutputChannels().
|
||||||
|
*/
|
||||||
|
virtual StringArray getOutputChannelNames() = 0;
|
||||||
|
|
||||||
|
/** Returns the names of all the available input channels on this device.
|
||||||
|
To find out which of these are currently in use, call getActiveInputChannels().
|
||||||
|
*/
|
||||||
|
virtual StringArray getInputChannelNames() = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the set of sample-rates this device supports.
|
||||||
|
@see getCurrentSampleRate
|
||||||
|
*/
|
||||||
|
virtual Array<double> getAvailableSampleRates() = 0;
|
||||||
|
|
||||||
|
/** Returns the set of buffer sizes that are available.
|
||||||
|
@see getCurrentBufferSizeSamples, getDefaultBufferSize
|
||||||
|
*/
|
||||||
|
virtual Array<int> getAvailableBufferSizes() = 0;
|
||||||
|
|
||||||
|
/** Returns the default buffer-size to use.
|
||||||
|
@returns a number of samples
|
||||||
|
@see getAvailableBufferSizes
|
||||||
|
*/
|
||||||
|
virtual int getDefaultBufferSize() = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Tries to open the device ready to play.
|
||||||
|
|
||||||
|
@param inputChannels a BigInteger in which a set bit indicates that the corresponding
|
||||||
|
input channel should be enabled
|
||||||
|
@param outputChannels a BigInteger in which a set bit indicates that the corresponding
|
||||||
|
output channel should be enabled
|
||||||
|
@param sampleRate the sample rate to try to use - to find out which rates are
|
||||||
|
available, see getAvailableSampleRates()
|
||||||
|
@param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer
|
||||||
|
sizes, see getAvailableBufferSizes()
|
||||||
|
@returns an error description if there's a problem, or an empty string if it succeeds in
|
||||||
|
opening the device
|
||||||
|
@see close
|
||||||
|
*/
|
||||||
|
virtual String open (const BigInteger& inputChannels,
|
||||||
|
const BigInteger& outputChannels,
|
||||||
|
double sampleRate,
|
||||||
|
int bufferSizeSamples) = 0;
|
||||||
|
|
||||||
|
/** Closes and releases the device if it's open. */
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
/** Returns true if the device is still open.
|
||||||
|
|
||||||
|
A device might spontaneously close itself if something goes wrong, so this checks if
|
||||||
|
it's still open.
|
||||||
|
*/
|
||||||
|
virtual bool isOpen() = 0;
|
||||||
|
|
||||||
|
/** Starts the device actually playing.
|
||||||
|
|
||||||
|
This must be called after the device has been opened.
|
||||||
|
|
||||||
|
@param callback the callback to use for streaming the data.
|
||||||
|
@see AudioIODeviceCallback, open
|
||||||
|
*/
|
||||||
|
virtual void start (AudioIODeviceCallback* callback) = 0;
|
||||||
|
|
||||||
|
/** Stops the device playing.
|
||||||
|
|
||||||
|
Once a device has been started, this will stop it. Any pending calls to the
|
||||||
|
callback class will be flushed before this method returns.
|
||||||
|
*/
|
||||||
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
/** Returns true if the device is still calling back.
|
||||||
|
|
||||||
|
The device might mysteriously stop, so this checks whether it's
|
||||||
|
still playing.
|
||||||
|
*/
|
||||||
|
virtual bool isPlaying() = 0;
|
||||||
|
|
||||||
|
/** Returns the last error that happened if anything went wrong. */
|
||||||
|
virtual String getLastError() = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the buffer size that the device is currently using.
|
||||||
|
|
||||||
|
If the device isn't actually open, this value doesn't really mean much.
|
||||||
|
*/
|
||||||
|
virtual int getCurrentBufferSizeSamples() = 0;
|
||||||
|
|
||||||
|
/** Returns the sample rate that the device is currently using.
|
||||||
|
|
||||||
|
If the device isn't actually open, this value doesn't really mean much.
|
||||||
|
*/
|
||||||
|
virtual double getCurrentSampleRate() = 0;
|
||||||
|
|
||||||
|
/** Returns the device's current physical bit-depth.
|
||||||
|
|
||||||
|
If the device isn't actually open, this value doesn't really mean much.
|
||||||
|
*/
|
||||||
|
virtual int getCurrentBitDepth() = 0;
|
||||||
|
|
||||||
|
/** Returns a mask showing which of the available output channels are currently
|
||||||
|
enabled.
|
||||||
|
@see getOutputChannelNames
|
||||||
|
*/
|
||||||
|
virtual BigInteger getActiveOutputChannels() const = 0;
|
||||||
|
|
||||||
|
/** Returns a mask showing which of the available input channels are currently
|
||||||
|
enabled.
|
||||||
|
@see getInputChannelNames
|
||||||
|
*/
|
||||||
|
virtual BigInteger getActiveInputChannels() const = 0;
|
||||||
|
|
||||||
|
/** Returns the device's output latency.
|
||||||
|
|
||||||
|
This is the delay in samples between a callback getting a block of data, and
|
||||||
|
that data actually getting played.
|
||||||
|
*/
|
||||||
|
virtual int getOutputLatencyInSamples() = 0;
|
||||||
|
|
||||||
|
/** Returns the device's input latency.
|
||||||
|
|
||||||
|
This is the delay in samples between some audio actually arriving at the soundcard,
|
||||||
|
and the callback getting passed this block of data.
|
||||||
|
*/
|
||||||
|
virtual int getInputLatencyInSamples() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** True if this device can show a pop-up control panel for editing its settings.
|
||||||
|
|
||||||
|
This is generally just true of ASIO devices. If true, you can call showControlPanel()
|
||||||
|
to display it.
|
||||||
|
*/
|
||||||
|
virtual bool hasControlPanel() const;
|
||||||
|
|
||||||
|
/** Shows a device-specific control panel if there is one.
|
||||||
|
|
||||||
|
This should only be called for devices which return true from hasControlPanel().
|
||||||
|
*/
|
||||||
|
virtual bool showControlPanel();
|
||||||
|
|
||||||
|
/** On devices which support it, this allows automatic gain control or other
|
||||||
|
mic processing to be disabled.
|
||||||
|
If the device doesn't support this operation, it'll return false.
|
||||||
|
*/
|
||||||
|
virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the number of under- or over runs reported by the OS since
|
||||||
|
playback/recording has started.
|
||||||
|
|
||||||
|
This number may be different than determining the Xrun count manually (by
|
||||||
|
measuring the time spent in the audio callback) as the OS may be doing
|
||||||
|
some buffering internally - especially on mobile devices.
|
||||||
|
|
||||||
|
Returns -1 if playback/recording has not started yet or if getting the underrun
|
||||||
|
count is not supported for this device (Android SDK 23 and lower).
|
||||||
|
*/
|
||||||
|
virtual int getXRunCount() const noexcept;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
protected:
|
||||||
|
/** Creates a device, setting its name and type member variables. */
|
||||||
|
AudioIODevice (const String& deviceName,
|
||||||
|
const String& typeName);
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
String name, typeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
AudioIODeviceType::AudioIODeviceType (const String& name)
|
||||||
|
: typeName (name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioIODeviceType::~AudioIODeviceType()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); }
|
||||||
|
void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); }
|
||||||
|
|
||||||
|
void AudioIODeviceType::callDeviceChangeListeners()
|
||||||
|
{
|
||||||
|
listeners.call ([] (Listener& l) { l.audioDeviceListChanged(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if ! JUCE_MAC
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! JUCE_IOS
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_WINDOWS && JUCE_WASAPI)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_WINDOWS && JUCE_ASIO)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_LINUX && JUCE_ALSA)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_LINUX && JUCE_JACK)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_LINUX && JUCE_BELA)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! JUCE_ANDROID
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OBOE)
|
||||||
|
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace juce
|
184
modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
Normal file
184
modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc.
|
||||||
|
|
||||||
|
To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes()
|
||||||
|
method. Each of the objects returned can then be used to list the available
|
||||||
|
devices of that type. E.g.
|
||||||
|
@code
|
||||||
|
OwnedArray<AudioIODeviceType> types;
|
||||||
|
myAudioDeviceManager.createAudioDeviceTypes (types);
|
||||||
|
|
||||||
|
for (int i = 0; i < types.size(); ++i)
|
||||||
|
{
|
||||||
|
String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc.
|
||||||
|
|
||||||
|
types[i]->scanForDevices(); // This must be called before getting the list of devices
|
||||||
|
|
||||||
|
StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type
|
||||||
|
|
||||||
|
for (int j = 0; j < deviceNames.size(); ++j)
|
||||||
|
{
|
||||||
|
AudioIODevice* device = types[i]->createDevice (deviceNames [j]);
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
For an easier way of managing audio devices and their settings, have a look at the
|
||||||
|
AudioDeviceManager class.
|
||||||
|
|
||||||
|
@see AudioIODevice, AudioDeviceManager
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API AudioIODeviceType
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the name of this type of driver that this object manages.
|
||||||
|
|
||||||
|
This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc.
|
||||||
|
*/
|
||||||
|
const String& getTypeName() const noexcept { return typeName; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Refreshes the object's cached list of known devices.
|
||||||
|
|
||||||
|
This must be called at least once before calling getDeviceNames() or any of
|
||||||
|
the other device creation methods.
|
||||||
|
*/
|
||||||
|
virtual void scanForDevices() = 0;
|
||||||
|
|
||||||
|
/** Returns the list of available devices of this type.
|
||||||
|
|
||||||
|
The scanForDevices() method must have been called to create this list.
|
||||||
|
|
||||||
|
@param wantInputNames only really used by DirectSound where devices are split up
|
||||||
|
into inputs and outputs, this indicates whether to use
|
||||||
|
the input or output name to refer to a pair of devices.
|
||||||
|
*/
|
||||||
|
virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0;
|
||||||
|
|
||||||
|
/** Returns the name of the default device.
|
||||||
|
|
||||||
|
This will be one of the names from the getDeviceNames() list.
|
||||||
|
|
||||||
|
@param forInput if true, this means that a default input device should be
|
||||||
|
returned; if false, it should return the default output
|
||||||
|
*/
|
||||||
|
virtual int getDefaultDeviceIndex (bool forInput) const = 0;
|
||||||
|
|
||||||
|
/** Returns the index of a given device in the list of device names.
|
||||||
|
If asInput is true, it shows the index in the inputs list, otherwise it
|
||||||
|
looks for it in the outputs list.
|
||||||
|
*/
|
||||||
|
virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0;
|
||||||
|
|
||||||
|
/** Returns true if two different devices can be used for the input and output.
|
||||||
|
*/
|
||||||
|
virtual bool hasSeparateInputsAndOutputs() const = 0;
|
||||||
|
|
||||||
|
/** Creates one of the devices of this type.
|
||||||
|
|
||||||
|
The deviceName must be one of the strings returned by getDeviceNames(), and
|
||||||
|
scanForDevices() must have been called before this method is used.
|
||||||
|
*/
|
||||||
|
virtual AudioIODevice* createDevice (const String& outputDeviceName,
|
||||||
|
const String& inputDeviceName) = 0;
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A class for receiving events when audio devices are inserted or removed.
|
||||||
|
|
||||||
|
You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object
|
||||||
|
using the AudioIODeviceType::addListener() method, and it will be called when
|
||||||
|
devices of that type are added or removed.
|
||||||
|
|
||||||
|
@see AudioIODeviceType::addListener, AudioIODeviceType::removeListener
|
||||||
|
*/
|
||||||
|
class Listener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Listener() {}
|
||||||
|
|
||||||
|
/** Called when the list of available audio devices changes. */
|
||||||
|
virtual void audioDeviceListChanged() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Adds a listener that will be called when this type of device is added or
|
||||||
|
removed from the system.
|
||||||
|
*/
|
||||||
|
void addListener (Listener* listener);
|
||||||
|
|
||||||
|
/** Removes a listener that was previously added with addListener(). */
|
||||||
|
void removeListener (Listener* listener);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~AudioIODeviceType();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a CoreAudio device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_CoreAudio();
|
||||||
|
/** Creates an iOS device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_iOSAudio();
|
||||||
|
/** Creates a WASAPI device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode);
|
||||||
|
/** Creates a DirectSound device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_DirectSound();
|
||||||
|
/** Creates an ASIO device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_ASIO();
|
||||||
|
/** Creates an ALSA device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_ALSA();
|
||||||
|
/** Creates a JACK device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_JACK();
|
||||||
|
/** Creates an Android device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_Android();
|
||||||
|
/** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_OpenSLES();
|
||||||
|
/** Creates an Oboe device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_Oboe();
|
||||||
|
/** Creates a Bela device type if it's available on this platform, or returns null. */
|
||||||
|
static AudioIODeviceType* createAudioIODeviceType_Bela();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit AudioIODeviceType (const String& typeName);
|
||||||
|
|
||||||
|
/** Synchronously calls all the registered device list change listeners. */
|
||||||
|
void callDeviceChangeListeners();
|
||||||
|
|
||||||
|
private:
|
||||||
|
String typeName;
|
||||||
|
ListenerList<Listener> listeners;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
59
modules/juce_audio_devices/audio_io/juce_SystemAudioVolume.h
Normal file
59
modules/juce_audio_devices/audio_io/juce_SystemAudioVolume.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Contains functions to control the system's master volume.
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API SystemAudioVolume
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns the operating system's current volume level in the range 0 to 1.0 */
|
||||||
|
static float JUCE_CALLTYPE getGain();
|
||||||
|
|
||||||
|
/** Attempts to set the operating system's current volume level.
|
||||||
|
@param newGain the level, between 0 and 1.0
|
||||||
|
@returns true if the operation succeeds
|
||||||
|
*/
|
||||||
|
static bool JUCE_CALLTYPE setGain (float newGain);
|
||||||
|
|
||||||
|
/** Returns true if the system's audio output is currently muted. */
|
||||||
|
static bool JUCE_CALLTYPE isMuted();
|
||||||
|
|
||||||
|
/** Attempts to mute the operating system's audio output.
|
||||||
|
@param shouldBeMuted true if you want it to be muted
|
||||||
|
@returns true if the operation succeeds
|
||||||
|
*/
|
||||||
|
static bool JUCE_CALLTYPE setMuted (bool shouldBeMuted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SystemAudioVolume(); // Don't instantiate this class, just call its static fns.
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (SystemAudioVolume)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
246
modules/juce_audio_devices/juce_audio_devices.cpp
Normal file
246
modules/juce_audio_devices/juce_audio_devices.cpp
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef JUCE_AUDIO_DEVICES_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
|
||||||
|
|
||||||
|
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||||
|
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
|
||||||
|
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||||
|
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||||
|
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_WINRT_MIDI
|
||||||
|
#define JUCE_USE_WINRT_MIDI 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_WINRT_MIDI
|
||||||
|
#define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "juce_audio_devices.h"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_MAC
|
||||||
|
#define Point CarbonDummyPointName
|
||||||
|
#define Component CarbonDummyCompName
|
||||||
|
#import <CoreAudio/AudioHardware.h>
|
||||||
|
#import <CoreMIDI/MIDIServices.h>
|
||||||
|
#import <AudioToolbox/AudioServices.h>
|
||||||
|
#undef Point
|
||||||
|
#undef Component
|
||||||
|
|
||||||
|
#elif JUCE_IOS
|
||||||
|
#import <AudioToolbox/AudioToolbox.h>
|
||||||
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
#import <CoreMIDI/MIDIServices.h>
|
||||||
|
|
||||||
|
#if TARGET_OS_SIMULATOR
|
||||||
|
#import <CoreMIDI/MIDINetworkSession.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_WINDOWS
|
||||||
|
#if JUCE_WASAPI
|
||||||
|
#include <mmreg.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_WINRT_MIDI
|
||||||
|
/* If you cannot find any of the header files below then you are probably
|
||||||
|
attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you
|
||||||
|
need to install version 10.0.14393.0 of the Windows Standalone SDK and add the
|
||||||
|
path to the WinRT headers to your build system. This path should have the form
|
||||||
|
"C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
|
||||||
|
|
||||||
|
Also please note that Microsoft's Bluetooth MIDI stack has multiple issues, so
|
||||||
|
this API is EXPERIMENTAL - use at your own risk!
|
||||||
|
*/
|
||||||
|
#include <windows.devices.h>
|
||||||
|
#include <windows.devices.midi.h>
|
||||||
|
#include <windows.devices.enumeration.h>
|
||||||
|
#include <wrl/event.h>
|
||||||
|
#if JUCE_MSVC
|
||||||
|
#pragma warning (push)
|
||||||
|
#pragma warning (disable: 4467)
|
||||||
|
#endif
|
||||||
|
#include <robuffer.h>
|
||||||
|
#if JUCE_MSVC
|
||||||
|
#pragma warning (pop)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_ASIO
|
||||||
|
/* This is very frustrating - we only need to use a handful of definitions from
|
||||||
|
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy
|
||||||
|
about 30 lines of code into this cpp file to create a fully stand-alone ASIO
|
||||||
|
implementation...
|
||||||
|
|
||||||
|
..unfortunately that would break Steinberg's license agreement for use of
|
||||||
|
their SDK, so I'm not allowed to do this.
|
||||||
|
|
||||||
|
This means that anyone who wants to use JUCE's ASIO abilities will have to:
|
||||||
|
|
||||||
|
1) Agree to Steinberg's licensing terms and download the ASIO SDK
|
||||||
|
(see http://www.steinberg.net/en/company/developers.html).
|
||||||
|
|
||||||
|
2) Enable this code with a global definition #define JUCE_ASIO 1.
|
||||||
|
|
||||||
|
3) Make sure that your header search path contains the iasiodrv.h file that
|
||||||
|
comes with the SDK. (Only about a handful of the SDK header files are actually
|
||||||
|
needed - so to simplify things, you could just copy these into your JUCE directory).
|
||||||
|
*/
|
||||||
|
#include <iasiodrv.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_LINUX
|
||||||
|
#if JUCE_ALSA
|
||||||
|
/* Got an include error here? If so, you've either not got ALSA installed, or you've
|
||||||
|
not got your paths set up correctly to find its header files.
|
||||||
|
|
||||||
|
The package you need to install to get ASLA support is "libasound2-dev".
|
||||||
|
|
||||||
|
If you don't have the ALSA library and don't want to build JUCE with audio support,
|
||||||
|
just set the JUCE_ALSA flag to 0.
|
||||||
|
*/
|
||||||
|
#include <alsa/asoundlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_JACK
|
||||||
|
/* Got an include error here? If so, you've either not got jack-audio-connection-kit
|
||||||
|
installed, or you've not got your paths set up correctly to find its header files.
|
||||||
|
|
||||||
|
The package you need to install to get JACK support is "libjack-dev".
|
||||||
|
|
||||||
|
If you don't have the jack-audio-connection-kit library and don't want to build
|
||||||
|
JUCE with low latency audio support, just set the JUCE_JACK flag to 0.
|
||||||
|
*/
|
||||||
|
#include <jack/jack.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_BELA
|
||||||
|
/* Got an include error here? If so, you've either not got the bela headers
|
||||||
|
installed, or you've not got your paths set up correctly to find its header
|
||||||
|
files.
|
||||||
|
*/
|
||||||
|
#include <Bela.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef SIZEOF
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_ANDROID
|
||||||
|
|
||||||
|
#if JUCE_USE_ANDROID_OPENSLES
|
||||||
|
#include <SLES/OpenSLES.h>
|
||||||
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
#include <SLES/OpenSLES_AndroidConfiguration.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_ANDROID_OBOE
|
||||||
|
#include <oboe/Oboe.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "audio_io/juce_AudioDeviceManager.cpp"
|
||||||
|
#include "audio_io/juce_AudioIODevice.cpp"
|
||||||
|
#include "audio_io/juce_AudioIODeviceType.cpp"
|
||||||
|
#include "midi_io/juce_MidiMessageCollector.cpp"
|
||||||
|
#include "midi_io/juce_MidiOutput.cpp"
|
||||||
|
#include "sources/juce_AudioSourcePlayer.cpp"
|
||||||
|
#include "sources/juce_AudioTransportSource.cpp"
|
||||||
|
#include "native/juce_MidiDataConcatenator.h"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#if JUCE_MAC
|
||||||
|
#include "native/juce_mac_CoreAudio.cpp"
|
||||||
|
#include "native/juce_mac_CoreMidi.cpp"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_IOS
|
||||||
|
#include "native/juce_ios_Audio.cpp"
|
||||||
|
#include "native/juce_mac_CoreMidi.cpp"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_WINDOWS
|
||||||
|
|
||||||
|
#if JUCE_WASAPI
|
||||||
|
#include "native/juce_win32_WASAPI.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_DIRECTSOUND
|
||||||
|
#include "native/juce_win32_DirectSound.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "native/juce_win32_Midi.cpp"
|
||||||
|
|
||||||
|
#if JUCE_ASIO
|
||||||
|
#include "native/juce_win32_ASIO.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_LINUX
|
||||||
|
#if JUCE_ALSA
|
||||||
|
#include "native/juce_linux_ALSA.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "native/juce_linux_Midi.cpp"
|
||||||
|
|
||||||
|
#if JUCE_JACK
|
||||||
|
#include "native/juce_linux_JackAudio.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_BELA
|
||||||
|
#include "native/juce_linux_Bela.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#elif JUCE_ANDROID
|
||||||
|
#include "native/juce_android_Audio.cpp"
|
||||||
|
#include "native/juce_android_Midi.cpp"
|
||||||
|
|
||||||
|
#if JUCE_USE_ANDROID_OPENSLES
|
||||||
|
#include "native/juce_android_OpenSL.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_ANDROID_OBOE
|
||||||
|
#include "native/juce_android_Oboe.cpp"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
|
||||||
|
namespace juce
|
||||||
|
{
|
||||||
|
// None of these methods are available. (On Windows you might need to enable WASAPI for this)
|
||||||
|
float JUCE_CALLTYPE SystemAudioVolume::getGain() { jassertfalse; return 0.0f; }
|
||||||
|
bool JUCE_CALLTYPE SystemAudioVolume::setGain (float) { jassertfalse; return false; }
|
||||||
|
bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { jassertfalse; return false; }
|
||||||
|
bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; }
|
||||||
|
}
|
||||||
|
#endif
|
189
modules/juce_audio_devices/juce_audio_devices.h
Normal file
189
modules/juce_audio_devices/juce_audio_devices.h
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
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_audio_devices
|
||||||
|
vendor: juce
|
||||||
|
version: 5.3.2
|
||||||
|
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: ISC
|
||||||
|
|
||||||
|
dependencies: juce_audio_basics, juce_events
|
||||||
|
OSXFrameworks: CoreAudio CoreMIDI AudioToolbox
|
||||||
|
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation
|
||||||
|
linuxPackages: alsa
|
||||||
|
mingwLibs: winmm
|
||||||
|
|
||||||
|
END_JUCE_MODULE_DECLARATION
|
||||||
|
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define JUCE_AUDIO_DEVICES_H_INCLUDED
|
||||||
|
|
||||||
|
#include <juce_events/juce_events.h>
|
||||||
|
#include <juce_audio_basics/juce_audio_basics.h>
|
||||||
|
|
||||||
|
#if JUCE_MODULE_AVAILABLE_juce_graphics
|
||||||
|
#include <juce_graphics/juce_graphics.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Config: JUCE_ASIO
|
||||||
|
Enables ASIO audio devices (MS Windows only).
|
||||||
|
Turning this on means that you'll need to have the Steinberg ASIO SDK installed
|
||||||
|
on your Windows build machine.
|
||||||
|
|
||||||
|
See the comments in the ASIOAudioIODevice class's header file for more
|
||||||
|
info about this.
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_ASIO
|
||||||
|
#define JUCE_ASIO 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_WASAPI
|
||||||
|
Enables WASAPI audio devices (Windows Vista and above). See also the
|
||||||
|
JUCE_WASAPI_EXCLUSIVE flag.
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_WASAPI
|
||||||
|
#define JUCE_WASAPI 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_WASAPI_EXCLUSIVE
|
||||||
|
Enables WASAPI audio devices in exclusive mode (Windows Vista and above).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_WASAPI_EXCLUSIVE
|
||||||
|
#define JUCE_WASAPI_EXCLUSIVE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/** Config: JUCE_DIRECTSOUND
|
||||||
|
Enables DirectSound audio (MS Windows only).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_DIRECTSOUND
|
||||||
|
#define JUCE_DIRECTSOUND 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_ALSA
|
||||||
|
Enables ALSA audio devices (Linux only).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_ALSA
|
||||||
|
#define JUCE_ALSA 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_JACK
|
||||||
|
Enables JACK audio devices (Linux only).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_JACK
|
||||||
|
#define JUCE_JACK 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_BELA
|
||||||
|
Enables Bela audio devices on Bela boards.
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_BELA
|
||||||
|
#define JUCE_BELA 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_USE_ANDROID_OBOE
|
||||||
|
***
|
||||||
|
DEVELOPER PREVIEW - Oboe is currently in developer preview and
|
||||||
|
is in active development. This preview allows for early access
|
||||||
|
and evaluation for developers targeting Android platform.
|
||||||
|
***
|
||||||
|
|
||||||
|
Enables Oboe devices (Android only, API 16 or above). Requires
|
||||||
|
Oboe repository path to be specified in Android exporter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_ANDROID_OBOE
|
||||||
|
#define JUCE_USE_ANDROID_OBOE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JUCE_USE_ANDROID_OBOE && JUCE_ANDROID_API_VERSION < 16
|
||||||
|
#undef JUCE_USE_ANDROID_OBOE
|
||||||
|
#define JUCE_USE_ANDROID_OBOE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_USE_ANDROID_OPENSLES
|
||||||
|
Enables OpenSLES devices (Android only).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_USE_ANDROID_OPENSLES
|
||||||
|
#if ! JUCE_USE_ANDROID_OBOE && JUCE_ANDROID_API_VERSION >= 9
|
||||||
|
#define JUCE_USE_ANDROID_OPENSLES 1
|
||||||
|
#else
|
||||||
|
#define JUCE_USE_ANDROID_OPENSLES 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_USE_WINRT_MIDI
|
||||||
|
***
|
||||||
|
EXPERIMENTAL - Microsoft's Bluetooth MIDI stack has multiple issues,
|
||||||
|
use at your own risk!
|
||||||
|
***
|
||||||
|
|
||||||
|
Enables the use of the Windows Runtime API for MIDI, which supports
|
||||||
|
Bluetooth Low Energy connections on computers with the Anniversary Update
|
||||||
|
of Windows 10.
|
||||||
|
|
||||||
|
To compile with this flag requires version 10.0.14393.0 of the Windows
|
||||||
|
Standalone SDK and you must add the path to the WinRT headers. This path
|
||||||
|
should be something similar to
|
||||||
|
"C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_USE_WINRT_MIDI
|
||||||
|
#define JUCE_USE_WINRT_MIDI 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Config: JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
|
||||||
|
Turning this on gives your app exclusive access to the system's audio
|
||||||
|
on platforms which support it (currently iOS only).
|
||||||
|
*/
|
||||||
|
#ifndef JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
|
||||||
|
#define JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#include "midi_io/juce_MidiInput.h"
|
||||||
|
#include "midi_io/juce_MidiMessageCollector.h"
|
||||||
|
#include "midi_io/juce_MidiOutput.h"
|
||||||
|
#include "audio_io/juce_AudioIODevice.h"
|
||||||
|
#include "audio_io/juce_AudioIODeviceType.h"
|
||||||
|
#include "audio_io/juce_SystemAudioVolume.h"
|
||||||
|
#include "sources/juce_AudioSourcePlayer.h"
|
||||||
|
#include "sources/juce_AudioTransportSource.h"
|
||||||
|
#include "audio_io/juce_AudioDeviceManager.h"
|
||||||
|
|
||||||
|
#if JUCE_IOS
|
||||||
|
#include "native/juce_ios_Audio.h"
|
||||||
|
#endif
|
23
modules/juce_audio_devices/juce_audio_devices.mm
Normal file
23
modules/juce_audio_devices/juce_audio_devices.mm
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "juce_audio_devices.cpp"
|
180
modules/juce_audio_devices/midi_io/juce_MidiInput.h
Normal file
180
modules/juce_audio_devices/midi_io/juce_MidiInput.h
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
class MidiInput;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Receives incoming messages from a physical MIDI input device.
|
||||||
|
|
||||||
|
This class is overridden to handle incoming midi messages. See the MidiInput
|
||||||
|
class for more details.
|
||||||
|
|
||||||
|
@see MidiInput
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiInputCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~MidiInputCallback() {}
|
||||||
|
|
||||||
|
|
||||||
|
/** Receives an incoming message.
|
||||||
|
|
||||||
|
A MidiInput object will call this method when a midi event arrives. It'll be
|
||||||
|
called on a high-priority system thread, so avoid doing anything time-consuming
|
||||||
|
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful
|
||||||
|
for queueing incoming messages for use later.
|
||||||
|
|
||||||
|
@param source the MidiInput object that generated the message
|
||||||
|
@param message the incoming message. The message's timestamp is set to a value
|
||||||
|
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the
|
||||||
|
time when the message arrived.
|
||||||
|
*/
|
||||||
|
virtual void handleIncomingMidiMessage (MidiInput* source,
|
||||||
|
const MidiMessage& message) = 0;
|
||||||
|
|
||||||
|
/** Notification sent each time a packet of a multi-packet sysex message arrives.
|
||||||
|
|
||||||
|
If a long sysex message is broken up into multiple packets, this callback is made
|
||||||
|
for each packet that arrives until the message is finished, at which point
|
||||||
|
the normal handleIncomingMidiMessage() callback will be made with the entire
|
||||||
|
message.
|
||||||
|
|
||||||
|
The message passed in will contain the start of a sysex, but won't be finished
|
||||||
|
with the terminating 0xf7 byte.
|
||||||
|
*/
|
||||||
|
virtual void handlePartialSysexMessage (MidiInput* source,
|
||||||
|
const uint8* messageData,
|
||||||
|
int numBytesSoFar,
|
||||||
|
double timestamp)
|
||||||
|
{
|
||||||
|
ignoreUnused (source, messageData, numBytesSoFar, timestamp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Represents a midi input device.
|
||||||
|
|
||||||
|
To create one of these, use the static getDevices() method to find out what inputs are
|
||||||
|
available, and then use the openDevice() method to try to open one.
|
||||||
|
|
||||||
|
@see MidiOutput
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiInput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Returns a list of the available midi input devices.
|
||||||
|
|
||||||
|
You can open one of the devices by passing its index into the
|
||||||
|
openDevice() method.
|
||||||
|
|
||||||
|
@see getDefaultDeviceIndex, openDevice
|
||||||
|
*/
|
||||||
|
static StringArray getDevices();
|
||||||
|
|
||||||
|
/** Returns the index of the default midi input device to use.
|
||||||
|
|
||||||
|
This refers to the index in the list returned by getDevices().
|
||||||
|
*/
|
||||||
|
static int getDefaultDeviceIndex();
|
||||||
|
|
||||||
|
/** Tries to open one of the midi input devices.
|
||||||
|
|
||||||
|
This will return a MidiInput object if it manages to open it. You can then
|
||||||
|
call start() and stop() on this device, and delete it when no longer needed.
|
||||||
|
|
||||||
|
If the device can't be opened, this will return a null pointer.
|
||||||
|
|
||||||
|
@param deviceIndex the index of a device from the list returned by getDevices()
|
||||||
|
@param callback the object that will receive the midi messages from this device.
|
||||||
|
|
||||||
|
@see MidiInputCallback, getDevices
|
||||||
|
*/
|
||||||
|
static MidiInput* openDevice (int deviceIndex,
|
||||||
|
MidiInputCallback* callback);
|
||||||
|
|
||||||
|
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN
|
||||||
|
/** This will try to create a new midi input device (Not available on Windows).
|
||||||
|
|
||||||
|
This will attempt to create a new midi input device with the specified name,
|
||||||
|
for other apps to connect to.
|
||||||
|
|
||||||
|
Returns nullptr if a device can't be created.
|
||||||
|
|
||||||
|
@param deviceName the name to use for the new device
|
||||||
|
@param callback the object that will receive the midi messages from this device.
|
||||||
|
*/
|
||||||
|
static MidiInput* createNewDevice (const String& deviceName,
|
||||||
|
MidiInputCallback* callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiInput();
|
||||||
|
|
||||||
|
/** Returns the name of this device. */
|
||||||
|
const String& getName() const noexcept { return name; }
|
||||||
|
|
||||||
|
/** Allows you to set a custom name for the device, in case you don't like the name
|
||||||
|
it was given when created.
|
||||||
|
*/
|
||||||
|
void setName (const String& newName) noexcept { name = newName; }
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Starts the device running.
|
||||||
|
|
||||||
|
After calling this, the device will start sending midi messages to the
|
||||||
|
MidiInputCallback object that was specified when the openDevice() method
|
||||||
|
was called.
|
||||||
|
|
||||||
|
@see stop
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/** Stops the device running.
|
||||||
|
@see start
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
String name;
|
||||||
|
void* internal = nullptr;
|
||||||
|
|
||||||
|
// The input objects are created with the openDevice() method.
|
||||||
|
explicit MidiInput (const String&);
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
158
modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp
Normal file
158
modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
MidiMessageCollector::MidiMessageCollector()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiMessageCollector::~MidiMessageCollector()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiMessageCollector::reset (const double newSampleRate)
|
||||||
|
{
|
||||||
|
jassert (newSampleRate > 0);
|
||||||
|
|
||||||
|
const ScopedLock sl (midiCallbackLock);
|
||||||
|
#if JUCE_DEBUG
|
||||||
|
hasCalledReset = true;
|
||||||
|
#endif
|
||||||
|
sampleRate = newSampleRate;
|
||||||
|
incomingMessages.clear();
|
||||||
|
lastCallbackTime = Time::getMillisecondCounterHiRes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageCollector::addMessageToQueue (const MidiMessage& message)
|
||||||
|
{
|
||||||
|
#if JUCE_DEBUG
|
||||||
|
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the messages that come in here need to be time-stamped correctly - see MidiInput
|
||||||
|
// for details of what the number should be.
|
||||||
|
jassert (message.getTimeStamp() != 0);
|
||||||
|
|
||||||
|
const ScopedLock sl (midiCallbackLock);
|
||||||
|
|
||||||
|
auto sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate);
|
||||||
|
|
||||||
|
incomingMessages.addEvent (message, sampleNumber);
|
||||||
|
|
||||||
|
// if the messages don't get used for over a second, we'd better
|
||||||
|
// get rid of any old ones to avoid the queue getting too big
|
||||||
|
if (sampleNumber > sampleRate)
|
||||||
|
incomingMessages.clear (0, sampleNumber - (int) sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer,
|
||||||
|
const int numSamples)
|
||||||
|
{
|
||||||
|
#if JUCE_DEBUG
|
||||||
|
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
|
||||||
|
#endif
|
||||||
|
|
||||||
|
jassert (numSamples > 0);
|
||||||
|
|
||||||
|
auto timeNow = Time::getMillisecondCounterHiRes();
|
||||||
|
auto msElapsed = timeNow - lastCallbackTime;
|
||||||
|
|
||||||
|
const ScopedLock sl (midiCallbackLock);
|
||||||
|
lastCallbackTime = timeNow;
|
||||||
|
|
||||||
|
if (! incomingMessages.isEmpty())
|
||||||
|
{
|
||||||
|
int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate));
|
||||||
|
int startSample = 0;
|
||||||
|
int scale = 1 << 16;
|
||||||
|
|
||||||
|
const uint8* midiData;
|
||||||
|
int numBytes, samplePosition;
|
||||||
|
|
||||||
|
MidiBuffer::Iterator iter (incomingMessages);
|
||||||
|
|
||||||
|
if (numSourceSamples > numSamples)
|
||||||
|
{
|
||||||
|
// if our list of events is longer than the buffer we're being
|
||||||
|
// asked for, scale them down to squeeze them all in..
|
||||||
|
const int maxBlockLengthToUse = numSamples << 5;
|
||||||
|
|
||||||
|
if (numSourceSamples > maxBlockLengthToUse)
|
||||||
|
{
|
||||||
|
startSample = numSourceSamples - maxBlockLengthToUse;
|
||||||
|
numSourceSamples = maxBlockLengthToUse;
|
||||||
|
iter.setNextSamplePosition (startSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale = (numSamples << 10) / numSourceSamples;
|
||||||
|
|
||||||
|
while (iter.getNextEvent (midiData, numBytes, samplePosition))
|
||||||
|
{
|
||||||
|
samplePosition = ((samplePosition - startSample) * scale) >> 10;
|
||||||
|
|
||||||
|
destBuffer.addEvent (midiData, numBytes,
|
||||||
|
jlimit (0, numSamples - 1, samplePosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if our event list is shorter than the number we need, put them
|
||||||
|
// towards the end of the buffer
|
||||||
|
startSample = numSamples - numSourceSamples;
|
||||||
|
|
||||||
|
while (iter.getNextEvent (midiData, numBytes, samplePosition))
|
||||||
|
{
|
||||||
|
destBuffer.addEvent (midiData, numBytes,
|
||||||
|
jlimit (0, numSamples - 1, samplePosition + startSample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
incomingMessages.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
|
||||||
|
{
|
||||||
|
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
|
||||||
|
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
|
||||||
|
|
||||||
|
addMessageToQueue (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
|
||||||
|
{
|
||||||
|
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
|
||||||
|
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
|
||||||
|
|
||||||
|
addMessageToQueue (m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
|
||||||
|
{
|
||||||
|
addMessageToQueue (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
105
modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.h
Normal file
105
modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
Collects incoming realtime MIDI messages and turns them into blocks suitable for
|
||||||
|
processing by a block-based audio callback.
|
||||||
|
|
||||||
|
The class can also be used as either a MidiKeyboardStateListener or a MidiInputCallback
|
||||||
|
so it can easily use a midi input or keyboard component as its source.
|
||||||
|
|
||||||
|
@see MidiMessage, MidiInput
|
||||||
|
|
||||||
|
@tags{Audio}
|
||||||
|
*/
|
||||||
|
class JUCE_API MidiMessageCollector : public MidiKeyboardStateListener,
|
||||||
|
public MidiInputCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
/** Creates a MidiMessageCollector. */
|
||||||
|
MidiMessageCollector();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
~MidiMessageCollector();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** Clears any messages from the queue.
|
||||||
|
|
||||||
|
You need to call this method before starting to use the collector, so that
|
||||||
|
it knows the correct sample rate to use.
|
||||||
|
*/
|
||||||
|
void reset (double sampleRate);
|
||||||
|
|
||||||
|
/** Takes an incoming real-time message and adds it to the queue.
|
||||||
|
|
||||||
|
The message's timestamp is taken, and it will be ready for retrieval as part
|
||||||
|
of the block returned by the next call to removeNextBlockOfMessages().
|
||||||
|
|
||||||
|
This method is fully thread-safe when overlapping calls are made with
|
||||||
|
removeNextBlockOfMessages().
|
||||||
|
*/
|
||||||
|
void addMessageToQueue (const MidiMessage& message);
|
||||||
|
|
||||||
|
/** Removes all the pending messages from the queue as a buffer.
|
||||||
|
|
||||||
|
This will also correct the messages' timestamps to make sure they're in
|
||||||
|
the range 0 to numSamples - 1.
|
||||||
|
|
||||||
|
This call should be made regularly by something like an audio processing
|
||||||
|
callback, because the time that it happens is used in calculating the
|
||||||
|
midi event positions.
|
||||||
|
|
||||||
|
This method is fully thread-safe when overlapping calls are made with
|
||||||
|
addMessageToQueue().
|
||||||
|
|
||||||
|
Precondition: numSamples must be greater than 0.
|
||||||
|
*/
|
||||||
|
void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples);
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** @internal */
|
||||||
|
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
|
||||||
|
/** @internal */
|
||||||
|
void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
|
||||||
|
/** @internal */
|
||||||
|
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
double lastCallbackTime = 0;
|
||||||
|
CriticalSection midiCallbackLock;
|
||||||
|
MidiBuffer incomingMessages;
|
||||||
|
double sampleRate = 44100.0;
|
||||||
|
#if JUCE_DEBUG
|
||||||
|
bool hasCalledReset = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
170
modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp
Normal file
170
modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
struct MidiOutput::PendingMessage
|
||||||
|
{
|
||||||
|
PendingMessage (const void* data, int len, double timeStamp)
|
||||||
|
: message (data, len, timeStamp)
|
||||||
|
{}
|
||||||
|
|
||||||
|
MidiMessage message;
|
||||||
|
PendingMessage* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
MidiOutput::MidiOutput (const String& deviceName)
|
||||||
|
: Thread ("midi out"), name (deviceName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer)
|
||||||
|
{
|
||||||
|
MidiBuffer::Iterator i (buffer);
|
||||||
|
MidiMessage message;
|
||||||
|
int samplePosition; // Note: not actually used, so no need to initialise.
|
||||||
|
|
||||||
|
while (i.getNextEvent (message, samplePosition))
|
||||||
|
sendMessageNow (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer,
|
||||||
|
double millisecondCounterToStartAt,
|
||||||
|
double samplesPerSecondForBuffer)
|
||||||
|
{
|
||||||
|
// You've got to call startBackgroundThread() for this to actually work..
|
||||||
|
jassert (isThreadRunning());
|
||||||
|
|
||||||
|
// this needs to be a value in the future - RTFM for this method!
|
||||||
|
jassert (millisecondCounterToStartAt > 0);
|
||||||
|
|
||||||
|
auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
|
||||||
|
|
||||||
|
const uint8* data;
|
||||||
|
int len, time;
|
||||||
|
|
||||||
|
for (MidiBuffer::Iterator i (buffer); i.getNextEvent (data, len, time);)
|
||||||
|
{
|
||||||
|
auto eventTime = millisecondCounterToStartAt + timeScaleFactor * time;
|
||||||
|
auto* m = new PendingMessage (data, len, eventTime);
|
||||||
|
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime)
|
||||||
|
{
|
||||||
|
m->next = firstMessage;
|
||||||
|
firstMessage = m;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto* mm = firstMessage;
|
||||||
|
|
||||||
|
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime)
|
||||||
|
mm = mm->next;
|
||||||
|
|
||||||
|
m->next = mm->next;
|
||||||
|
mm->next = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::clearAllPendingMessages()
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
|
||||||
|
while (firstMessage != nullptr)
|
||||||
|
{
|
||||||
|
auto* m = firstMessage;
|
||||||
|
firstMessage = firstMessage->next;
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::startBackgroundThread()
|
||||||
|
{
|
||||||
|
startThread (9);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::stopBackgroundThread()
|
||||||
|
{
|
||||||
|
stopThread (5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiOutput::run()
|
||||||
|
{
|
||||||
|
while (! threadShouldExit())
|
||||||
|
{
|
||||||
|
uint32 now = Time::getMillisecondCounter();
|
||||||
|
uint32 eventTime = 0;
|
||||||
|
uint32 timeToWait = 500;
|
||||||
|
|
||||||
|
PendingMessage* message;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
message = firstMessage;
|
||||||
|
|
||||||
|
if (message != nullptr)
|
||||||
|
{
|
||||||
|
eventTime = (uint32) roundToInt (message->message.getTimeStamp());
|
||||||
|
|
||||||
|
if (eventTime > now + 20)
|
||||||
|
{
|
||||||
|
timeToWait = eventTime - (now + 20);
|
||||||
|
message = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstMessage = message->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message != nullptr)
|
||||||
|
{
|
||||||
|
std::unique_ptr<PendingMessage> messageDeleter (message);
|
||||||
|
|
||||||
|
if (eventTime > now)
|
||||||
|
{
|
||||||
|
Time::waitForMillisecondCounter (eventTime);
|
||||||
|
|
||||||
|
if (threadShouldExit())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventTime > now - 200)
|
||||||
|
sendMessageNow (message->message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jassert (timeToWait < 1000 * 30);
|
||||||
|
wait ((int) timeToWait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllPendingMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace juce
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user