Signed-off-by: falkTX <falktx@falktx.com>tags/2020-12-27
@@ -0,0 +1,518 @@ | |||||
/* | |||||
============================================================================== | |||||
Build options for juce static library | |||||
============================================================================== | |||||
*/ | |||||
#ifndef BUILD_JUCE_APPCONFIG_H_INCLUDED | |||||
#define BUILD_JUCE_APPCONFIG_H_INCLUDED | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_formats 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_plugin_client 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_processors 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_audio_utils 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_blocks_basics 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_box2d 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_core 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_cryptography 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_data_structures 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_dsp 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_events 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_graphics 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_gui_basics 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_gui_extra 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_opengl 1 | |||||
#define JUCE_MODULE_AVAILABLE_juce_osc 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_product_unlocking 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_video 0 | |||||
//============================================================================= | |||||
#define JUCE_STANDALONE_APPLICATION 0 | |||||
#define JUCE_REPORT_APP_USAGE 0 | |||||
#define JUCE_DISPLAY_SPLASH_SCREEN 0 | |||||
#define JUCE_USE_DARK_SPLASH_SCREEN 0 | |||||
//============================================================================= | |||||
// juce_audio_basics | |||||
//============================================================================= | |||||
// juce_audio_devices | |||||
/** 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. | |||||
*/ | |||||
#define JUCE_ASIO 0 | |||||
/** Config: JUCE_WASAPI | |||||
Enables WASAPI audio devices (Windows Vista and above). | |||||
*/ | |||||
#define JUCE_WASAPI 0 | |||||
/** Config: JUCE_DIRECTSOUND | |||||
Enables DirectSound audio (MS Windows only). | |||||
*/ | |||||
#define JUCE_DIRECTSOUND 0 | |||||
/** Config: JUCE_ALSA | |||||
Enables ALSA audio devices (Linux only). | |||||
*/ | |||||
#if LINUX | |||||
#define JUCE_ALSA 1 | |||||
#define JUCE_ALSA_MIDI_NAME "JuceMidi" | |||||
#else | |||||
#define JUCE_ALSA 0 | |||||
#endif | |||||
/** Config: JUCE_JACK | |||||
Enables JACK audio devices (Linux only). | |||||
*/ | |||||
#define JUCE_JACK 0 | |||||
/** Config: JUCE_USE_ANDROID_OPENSLES | |||||
Enables OpenSLES devices (Android only). | |||||
*/ | |||||
#define JUCE_USE_ANDROID_OPENSLES 0 | |||||
/** Config: JUCE_USE_CDREADER | |||||
Enables the AudioCDReader class (on supported platforms). | |||||
*/ | |||||
#define JUCE_USE_CDREADER 0 | |||||
/** Config: JUCE_USE_CDBURNER | |||||
Enables the AudioCDBurner class (on supported platforms). | |||||
*/ | |||||
#define JUCE_USE_CDBURNER 0 | |||||
//============================================================================= | |||||
// juce_audio_formats | |||||
/** Config: JUCE_USE_FLAC | |||||
Enables the FLAC audio codec classes (available on all platforms). | |||||
If your app doesn't need to read FLAC files, you might want to disable this to | |||||
reduce the size of your codebase and build time. | |||||
*/ | |||||
#define JUCE_USE_FLAC 1 | |||||
/** Config: JUCE_USE_OGGVORBIS | |||||
Enables the Ogg-Vorbis audio codec classes (available on all platforms). | |||||
If your app doesn't need to read Ogg-Vorbis files, you might want to disable this to | |||||
reduce the size of your codebase and build time. | |||||
*/ | |||||
#define JUCE_USE_OGGVORBIS 1 | |||||
/** Config: JUCE_USE_MP3AUDIOFORMAT | |||||
Enables the software-based MP3AudioFormat class. | |||||
IMPORTANT DISCLAIMER: By choosing to enable the JUCE_USE_MP3AUDIOFORMAT flag and to compile | |||||
this MP3 code into your software, you do so AT YOUR OWN RISK! By doing so, you are agreeing | |||||
that Raw Material Software is in no way responsible for any patent, copyright, or other | |||||
legal issues that you may suffer as a result. | |||||
The code in juce_MP3AudioFormat.cpp is NOT guaranteed to be free from infringements of 3rd-party | |||||
intellectual property. If you wish to use it, please seek your own independent advice about the | |||||
legality of doing so. If you are not willing to accept full responsibility for the consequences | |||||
of using this code, then do not enable this setting. | |||||
*/ | |||||
#define JUCE_USE_MP3AUDIOFORMAT 0 | |||||
/** Config: JUCE_USE_LAME_AUDIO_FORMAT | |||||
Enables the LameEncoderAudioFormat class. | |||||
*/ | |||||
#define JUCE_USE_LAME_AUDIO_FORMAT 0 | |||||
/** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT | |||||
Enables the Windows Media SDK codecs. | |||||
*/ | |||||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | |||||
//============================================================================= | |||||
// juce_audio_processors | |||||
/** Config: JUCE_PLUGINHOST_VST | |||||
Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||||
installed on your machine. | |||||
@see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | |||||
*/ | |||||
#define JUCE_PLUGINHOST_VST 1 | |||||
/** Config: JUCE_PLUGINHOST_VST3 | |||||
Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be | |||||
installed on your machine. | |||||
@see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU | |||||
*/ | |||||
#if 0 //MAC || WINDOWS | |||||
#define JUCE_PLUGINHOST_VST3 1 | |||||
#else | |||||
#define JUCE_PLUGINHOST_VST3 0 | |||||
#endif | |||||
/** Config: JUCE_PLUGINHOST_AU | |||||
Enables the AudioUnit plugin hosting classes. This is Mac-only, of course. | |||||
@see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_VST3 | |||||
*/ | |||||
#if MAC | |||||
#define JUCE_PLUGINHOST_AU 1 | |||||
#else | |||||
#define JUCE_PLUGINHOST_AU 0 | |||||
#endif | |||||
#if LINUX | |||||
#define JUCE_PLUGINHOST_LADSPA 1 | |||||
#else | |||||
#define JUCE_PLUGINHOST_LADSPA 0 | |||||
#endif | |||||
//============================================================================= | |||||
// juce_audio_utils | |||||
//============================================================================= | |||||
// juce_core | |||||
/** Config: JUCE_FORCE_DEBUG | |||||
Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, | |||||
but if you define this value, you can override this to force it to be true or false. | |||||
*/ | |||||
#define JUCE_FORCE_DEBUG 0 | |||||
/** Config: JUCE_LOG_ASSERTIONS | |||||
If this flag is enabled, the the jassert and jassertfalse macros will always use Logger::writeToLog() | |||||
to write a message when an assertion happens. | |||||
Enabling it will also leave this turned on in release builds. When it's disabled, | |||||
however, the jassert and jassertfalse macros will not be compiled in a | |||||
release build. | |||||
@see jassert, jassertfalse, Logger | |||||
*/ | |||||
#define JUCE_LOG_ASSERTIONS 1 | |||||
/** Config: JUCE_CHECK_MEMORY_LEAKS | |||||
Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector | |||||
class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. | |||||
*/ | |||||
#ifdef DEBUG | |||||
#define JUCE_CHECK_MEMORY_LEAKS 1 | |||||
#else | |||||
#define JUCE_CHECK_MEMORY_LEAKS 0 | |||||
#endif | |||||
/** Config: JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
In a Visual C++ build, this can be used to stop the required system libs being | |||||
automatically added to the link stage. | |||||
*/ | |||||
#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 | |||||
/* Config: JUCE_INCLUDE_ZLIB_CODE | |||||
This can be used to disable Juce's embedded 3rd-party zlib code. | |||||
You might need to tweak this if you're linking to an external zlib library in your app, | |||||
but for normal apps, this option should be left alone. | |||||
If you disable this, you might also want to set a value for JUCE_ZLIB_INCLUDE_PATH, to | |||||
specify the path where your zlib headers live. | |||||
*/ | |||||
#define JUCE_INCLUDE_ZLIB_CODE 1 | |||||
/** Config: JUCE_USE_CURL | |||||
Enables http/https support via libcurl (Linux only). Enabling this will add an additional | |||||
run-time dynmic dependency to libcurl. | |||||
If you disable this then https/ssl support will not be available on linux. | |||||
*/ | |||||
#define JUCE_USE_CURL 0 | |||||
/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS | |||||
If enabled, this will add some exception-catching code to forward unhandled exceptions | |||||
to your JUCEApplication::unhandledException() callback. | |||||
*/ | |||||
#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0 | |||||
/** Config: JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
If disabled, this will turn off dangerous static globals like String::empty, var::null, etc | |||||
which can cause nasty order-of-initialisation problems if they are referenced during static | |||||
constructor code. | |||||
*/ | |||||
#define JUCE_ALLOW_STATIC_NULL_VARIABLES 0 | |||||
#define JUCE_STRING_UTF_TYPE 8 | |||||
#define JUCE_USE_VFORK 1 | |||||
//============================================================================= | |||||
// juce_cryptography | |||||
//============================================================================= | |||||
// juce_data_structures | |||||
//============================================================================= | |||||
// juce_dsp | |||||
/** Config: JUCE_ASSERTION_FIRFILTER | |||||
When this flag is enabled, an assertion will be generated during the | |||||
execution of DEBUG configurations if you use a FIRFilter class to process | |||||
FIRCoefficients with a size higher than 128, to tell you that's it would be | |||||
more efficient to use the Convolution class instead. It is enabled by | |||||
default, but you may want to disable it if you really want to process such | |||||
a filter in the time domain. | |||||
*/ | |||||
#define JUCE_ASSERTION_FIRFILTER 0 | |||||
/** Config: JUCE_DSP_USE_INTEL_MKL | |||||
If this flag is set, then JUCE will use Intel's MKL for JUCE's FFT and | |||||
convolution classes. | |||||
The folder containing the mkl_dfti.h header must be in your header | |||||
search paths when using this flag. You also need to add all the necessary | |||||
intel mkl libraries to the "External Libraries to Link" field in the | |||||
Projucer. | |||||
*/ | |||||
#define JUCE_DSP_USE_INTEL_MKL 0 | |||||
/** Config: JUCE_DSP_USE_SHARED_FFTW | |||||
If this flag is set, then JUCE will search for the fftw shared libraries | |||||
at runtime and use the library for JUCE's FFT and convolution classes. | |||||
If the library is not found, then JUCE's fallback FFT routines will be used. | |||||
This is especially useful on linux as fftw often comes pre-installed on | |||||
popular linux distros. | |||||
You must respect the FFTW license when enabling this option. | |||||
*/ | |||||
#define JUCE_DSP_USE_SHARED_FFTW 0 | |||||
/** Config: JUCE_DSP_USE_STATIC_FFTW | |||||
If this flag is set, then JUCE will use the statically linked fftw libraries | |||||
for JUCE's FFT and convolution classes. | |||||
You must add the fftw header/library folder to the extra header/library search | |||||
paths of your JUCE project. You also need to add the fftw library itself | |||||
to the extra libraries supplied to your JUCE project during linking. | |||||
You must respect the FFTW license when enabling this option. | |||||
*/ | |||||
#define JUCE_DSP_USE_STATIC_FFTW 0 | |||||
/** Config: JUCE_DSP_ENABLE_SNAP_TO_ZERO | |||||
Enables code in the dsp module to avoid floating point denormals during the | |||||
processing of some of the dsp module's filters. | |||||
Enabling this will add a slight performance overhead to the DSP module's | |||||
filters and algorithms. If your audio app already disables denormals altogether | |||||
(for exmaple, by using the ScopedNoDenormals class or the | |||||
FloatVectorOperations::disableDenormalisedNumberSupport method), then you | |||||
can safely disable this flag to shave off a few cpu cycles from the DSP module's | |||||
filters and algorithms. | |||||
*/ | |||||
#define JUCE_DSP_ENABLE_SNAP_TO_ZERO 0 | |||||
//============================================================================= | |||||
// juce_events | |||||
//============================================================================= | |||||
// juce_graphics | |||||
//============================================================================= | |||||
/** Config: JUCE_USE_COREIMAGE_LOADER | |||||
On OSX, enabling this flag means that the CoreImage codecs will be used to load | |||||
PNG/JPEG/GIF files. It is enabled by default, but you may want to disable it if | |||||
you'd rather use libpng, libjpeg, etc. | |||||
*/ | |||||
#if MAC | |||||
#define JUCE_USE_COREIMAGE_LOADER 1 | |||||
#else | |||||
#define JUCE_USE_COREIMAGE_LOADER 0 | |||||
#endif | |||||
/** Config: JUCE_USE_DIRECTWRITE | |||||
Enabling this flag means that DirectWrite will be used when available for font | |||||
management and layout. | |||||
*/ | |||||
#define JUCE_USE_DIRECTWRITE 0 | |||||
#define JUCE_INCLUDE_PNGLIB_CODE 1 | |||||
#define JUCE_INCLUDE_JPEGLIB_CODE 1 | |||||
#if MAC | |||||
#define USE_COREGRAPHICS_RENDERING 1 | |||||
#else | |||||
#define USE_COREGRAPHICS_RENDERING 0 | |||||
#endif | |||||
//============================================================================= | |||||
// juce_gui_basics | |||||
/** Config: JUCE_ENABLE_REPAINT_DEBUGGING | |||||
If this option is turned on, each area of the screen that gets repainted will | |||||
flash in a random colour, so that you can see exactly which bits of your | |||||
components are being drawn. | |||||
*/ | |||||
#define JUCE_ENABLE_REPAINT_DEBUGGING 0 | |||||
/** JUCE_USE_XRANDR: Enables Xrandr multi-monitor support (Linux only). | |||||
Unless you specifically want to disable this, it's best to leave this option turned on. | |||||
Note that your users do not need to have Xrandr installed for your JUCE app to run, as | |||||
the availability of Xrandr is queried during runtime. | |||||
*/ | |||||
#define JUCE_USE_XRANDR 0 | |||||
/** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only). | |||||
Unless you specifically want to disable this, it's best to leave this option turned on. | |||||
*/ | |||||
#define JUCE_USE_XINERAMA 0 | |||||
/** Config: JUCE_USE_XSHM | |||||
Enables X shared memory for faster rendering on Linux. This is best left turned on | |||||
unless you have a good reason to disable it. | |||||
*/ | |||||
#if LINUX | |||||
#define JUCE_USE_XSHM 1 | |||||
#else | |||||
#define JUCE_USE_XSHM 0 | |||||
#endif | |||||
/** Config: JUCE_USE_XRENDER | |||||
Enables XRender to allow semi-transparent windowing on Linux. | |||||
*/ | |||||
#define JUCE_USE_XRENDER 0 | |||||
/** Config: JUCE_USE_XCURSOR | |||||
Uses XCursor to allow ARGB cursor on Linux. This is best left turned on unless you have | |||||
a good reason to disable it. | |||||
*/ | |||||
#if LINUX | |||||
#define JUCE_USE_XCURSOR 1 | |||||
#else | |||||
#define JUCE_USE_XCURSOR 0 | |||||
#endif | |||||
//============================================================================= | |||||
// juce_gui_extra | |||||
/** Config: JUCE_WEB_BROWSER | |||||
This lets you disable the WebBrowserComponent class (Mac and Windows). | |||||
If you're not using any embedded web-pages, turning this off may reduce your code size. | |||||
*/ | |||||
#define JUCE_WEB_BROWSER 0 | |||||
/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR | |||||
This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support. See the documentation | |||||
for that macro for more details. | |||||
*/ | |||||
#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 0 | |||||
//============================================================================= | |||||
// juce_opengl | |||||
//============================================================================= | |||||
// drowaudio | |||||
/** Config: DROWAUDIO_USE_FFTREAL | |||||
Enables the FFTReal library. By default this is enabled except on the Mac | |||||
where the Accelerate framework is preferred. However, if you do explicity | |||||
enable this setting fftreal can be used for testing purposes. | |||||
*/ | |||||
#if JUCE_MAC | |||||
#define DROWAUDIO_USE_FFTREAL 0 | |||||
#else | |||||
#define DROWAUDIO_USE_FFTREAL 1 | |||||
#endif | |||||
/** Config: DROWAUDIO_USE_SOUNDTOUCH | |||||
Enables the SoundTouch library and the associated SoundTouch classes for | |||||
independant pitch and tempo scaling. By default this is enabled. | |||||
*/ | |||||
#define DROWAUDIO_USE_SOUNDTOUCH 1 | |||||
/** Config: DROWAUDIO_USE_CURL | |||||
Enables the cURL library and the associated network classes. By default | |||||
this is enabled. | |||||
*/ | |||||
#define DROWAUDIO_USE_CURL 0 | |||||
//============================================================================= | |||||
// juced | |||||
/** Config: JUCE_LASH | |||||
Enables LASH support on Linux. | |||||
Not enabled by default. | |||||
*/ | |||||
#define JUCE_LASH 0 | |||||
/** Config: JUCE_USE_GLX | |||||
Enable this under Linux to use GLX for fast openGL rendering with alpha | |||||
compositing support over a composite manager (compiz / xcompmgr). | |||||
Not enabled by default. | |||||
*/ | |||||
#define JUCE_USE_GLX 0 | |||||
/** Config: JUCE_SUPPORT_SQLITE | |||||
Setting this allows the build to use SQLITE libraries for access a self-contained, | |||||
serverless, zero-configuration, transactional SQL database engine. | |||||
Not enabled by default. | |||||
*/ | |||||
#define JUCE_SUPPORT_SQLITE 0 | |||||
/** Config: JUCE_SUPPORT_SCRIPTING | |||||
Setting this allows the build to use Angelscript library for using scripting | |||||
inside the juce library itself | |||||
Not enabled by default. | |||||
*/ | |||||
#define JUCE_SUPPORT_SCRIPTING 0 | |||||
/** Config: JUCETICE_INCLUDE_ANGELSCRIPT_CODE | |||||
Enables direct inclusion of the angelscript library. | |||||
Enabled by default. | |||||
*/ | |||||
#define JUCETICE_INCLUDE_ANGELSCRIPT_CODE 0 | |||||
/** Config: JUCETICE_INCLUDE_CURL_CODE | |||||
Enables direct inclusion of curl. | |||||
// Currently not available // | |||||
*/ | |||||
#define JUCETICE_INCLUDE_CURL_CODE 0 | |||||
//============================================================================= | |||||
// Linux embed build | |||||
#if JUCE_AUDIOPROCESSOR_NO_GUI | |||||
#undef JUCE_MODULE_AVAILABLE_juce_graphics | |||||
#undef JUCE_MODULE_AVAILABLE_juce_gui_basics | |||||
#undef JUCE_MODULE_AVAILABLE_juce_gui_extra | |||||
#undef JUCE_MODULE_AVAILABLE_juce_opengl | |||||
#define JUCE_MODULE_AVAILABLE_juce_graphics 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_gui_basics 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_gui_extra 0 | |||||
#define JUCE_MODULE_AVAILABLE_juce_opengl 0 | |||||
#undef JUCE_ALSA | |||||
#undef JUCE_PLUGINHOST_LADSPA | |||||
#undef JUCE_PLUGINHOST_VST | |||||
#define JUCE_ALSA 0 | |||||
#define JUCE_PLUGINHOST_LADSPA 0 | |||||
#define JUCE_PLUGINHOST_VST 0 | |||||
#endif | |||||
#endif // BUILD_JUCE_APPCONFIG_H_INCLUDED |
@@ -0,0 +1,49 @@ | |||||
############################################################################### | |||||
if linux_embed | |||||
juce_current_srcs = [ | |||||
'source/modules/juce_audio_basics/juce_audio_basics.cpp', | |||||
'source/modules/juce_audio_devices/juce_audio_devices.cpp', | |||||
'source/modules/juce_audio_formats/juce_audio_formats.cpp', | |||||
'source/modules/juce_audio_processors/juce_audio_processors.cpp', | |||||
'source/modules/juce_audio_utils/juce_audio_utils.cpp', | |||||
'source/modules/juce_core/juce_core.cpp', | |||||
'source/modules/juce_cryptography/juce_cryptography.cpp', | |||||
'source/modules/juce_data_structures/juce_data_structures.cpp', | |||||
'source/modules/juce_dsp/juce_dsp.cpp', | |||||
'source/modules/juce_events/juce_events.cpp', | |||||
] | |||||
else | |||||
juce_current_srcs = [ | |||||
'source/modules/juce_audio_basics/juce_audio_basics.cpp', | |||||
'source/modules/juce_audio_devices/juce_audio_devices.cpp', | |||||
'source/modules/juce_audio_formats/juce_audio_formats.cpp', | |||||
'source/modules/juce_audio_processors/juce_audio_processors.cpp', | |||||
'source/modules/juce_audio_utils/juce_audio_utils.cpp', | |||||
'source/modules/juce_core/juce_core.cpp', | |||||
'source/modules/juce_cryptography/juce_cryptography.cpp', | |||||
'source/modules/juce_data_structures/juce_data_structures.cpp', | |||||
'source/modules/juce_dsp/juce_dsp.cpp', | |||||
'source/modules/juce_events/juce_events.cpp', | |||||
'source/modules/juce_graphics/juce_graphics.cpp', | |||||
'source/modules/juce_gui_basics/juce_gui_basics.cpp', | |||||
'source/modules/juce_gui_extra/juce_gui_extra.cpp', | |||||
'source/modules/juce_opengl/juce_opengl.cpp', | |||||
] | |||||
endif | |||||
lib_juce_current = static_library('juce-current', | |||||
sources: juce_current_srcs, | |||||
include_directories: [ | |||||
include_directories('.'), | |||||
include_directories('source'), | |||||
include_directories('source' / 'modules'), | |||||
include_directories('..' / 'juced' / 'source' / 'dependancies' / 'ladspa_sdk' / 'src'), | |||||
], | |||||
cpp_args: build_flags_cpp, | |||||
dependencies: dependencies, | |||||
pic: true, | |||||
install: false, | |||||
) | |||||
############################################################################### |
@@ -0,0 +1,157 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
public: | |||||
virtual ~AudioPlayHead() = default; | |||||
//============================================================================== | |||||
/** 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 |
@@ -0,0 +1,723 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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"); | |||||
case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4"); | |||||
case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5"); | |||||
case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6"); | |||||
case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7"); | |||||
case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8"); | |||||
case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9"); | |||||
case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10"); | |||||
case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11"); | |||||
case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12"); | |||||
case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13"); | |||||
case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14"); | |||||
case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15"); | |||||
case ambisonicACN16: return NEEDS_TRANS("Ambisonic 16"); | |||||
case ambisonicACN17: return NEEDS_TRANS("Ambisonic 17"); | |||||
case ambisonicACN18: return NEEDS_TRANS("Ambisonic 18"); | |||||
case ambisonicACN19: return NEEDS_TRANS("Ambisonic 19"); | |||||
case ambisonicACN20: return NEEDS_TRANS("Ambisonic 20"); | |||||
case ambisonicACN21: return NEEDS_TRANS("Ambisonic 21"); | |||||
case ambisonicACN22: return NEEDS_TRANS("Ambisonic 22"); | |||||
case ambisonicACN23: return NEEDS_TRANS("Ambisonic 23"); | |||||
case ambisonicACN24: return NEEDS_TRANS("Ambisonic 24"); | |||||
case ambisonicACN25: return NEEDS_TRANS("Ambisonic 25"); | |||||
case ambisonicACN26: return NEEDS_TRANS("Ambisonic 26"); | |||||
case ambisonicACN27: return NEEDS_TRANS("Ambisonic 27"); | |||||
case ambisonicACN28: return NEEDS_TRANS("Ambisonic 28"); | |||||
case ambisonicACN29: return NEEDS_TRANS("Ambisonic 29"); | |||||
case ambisonicACN30: return NEEDS_TRANS("Ambisonic 30"); | |||||
case ambisonicACN31: return NEEDS_TRANS("Ambisonic 31"); | |||||
case ambisonicACN32: return NEEDS_TRANS("Ambisonic 32"); | |||||
case ambisonicACN33: return NEEDS_TRANS("Ambisonic 33"); | |||||
case ambisonicACN34: return NEEDS_TRANS("Ambisonic 34"); | |||||
case ambisonicACN35: return NEEDS_TRANS("Ambisonic 35"); | |||||
case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); | |||||
case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); | |||||
case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); | |||||
case proximityLeft: return NEEDS_TRANS("Proximity Left"); | |||||
case proximityRight: return NEEDS_TRANS("Proximity Right"); | |||||
case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left"); | |||||
case bottomSideRight: return NEEDS_TRANS("Bottom Side Right"); | |||||
case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left"); | |||||
case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre"); | |||||
case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right"); | |||||
case discreteChannel0: | |||||
case unknown: | |||||
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 ambisonicACN4: return "ACN4"; | |||||
case ambisonicACN5: return "ACN5"; | |||||
case ambisonicACN6: return "ACN6"; | |||||
case ambisonicACN7: return "ACN7"; | |||||
case ambisonicACN8: return "ACN8"; | |||||
case ambisonicACN9: return "ACN9"; | |||||
case ambisonicACN10: return "ACN10"; | |||||
case ambisonicACN11: return "ACN11"; | |||||
case ambisonicACN12: return "ACN12"; | |||||
case ambisonicACN13: return "ACN13"; | |||||
case ambisonicACN14: return "ACN14"; | |||||
case ambisonicACN15: return "ACN15"; | |||||
case ambisonicACN16: return "ACN16"; | |||||
case ambisonicACN17: return "ACN17"; | |||||
case ambisonicACN18: return "ACN18"; | |||||
case ambisonicACN19: return "ACN19"; | |||||
case ambisonicACN20: return "ACN20"; | |||||
case ambisonicACN21: return "ACN21"; | |||||
case ambisonicACN22: return "ACN22"; | |||||
case ambisonicACN23: return "ACN23"; | |||||
case ambisonicACN24: return "ACN24"; | |||||
case ambisonicACN25: return "ACN25"; | |||||
case ambisonicACN26: return "ACN26"; | |||||
case ambisonicACN27: return "ACN27"; | |||||
case ambisonicACN28: return "ACN28"; | |||||
case ambisonicACN29: return "ACN29"; | |||||
case ambisonicACN30: return "ACN30"; | |||||
case ambisonicACN31: return "ACN31"; | |||||
case ambisonicACN32: return "ACN32"; | |||||
case ambisonicACN33: return "ACN33"; | |||||
case ambisonicACN34: return "ACN34"; | |||||
case ambisonicACN35: return "ACN35"; | |||||
case topSideLeft: return "Tsl"; | |||||
case topSideRight: return "Tsr"; | |||||
case bottomFrontLeft: return "Bfl"; | |||||
case bottomFrontCentre: return "Bfc"; | |||||
case bottomFrontRight: return "Bfr"; | |||||
case proximityLeft: return "Pl"; | |||||
case proximityRight: return "Pr"; | |||||
case bottomSideLeft: return "Bsl"; | |||||
case bottomSideRight: return "Bsr"; | |||||
case bottomRearLeft: return "Brl"; | |||||
case bottomRearCentre: return "Brc"; | |||||
case bottomRearRight: return "Brr"; | |||||
case discreteChannel0: | |||||
case unknown: | |||||
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 == "ACN0") return ambisonicACN0; | |||||
if (abbr == "ACN1") return ambisonicACN1; | |||||
if (abbr == "ACN2") return ambisonicACN2; | |||||
if (abbr == "ACN3") return ambisonicACN3; | |||||
if (abbr == "ACN4") return ambisonicACN4; | |||||
if (abbr == "ACN5") return ambisonicACN5; | |||||
if (abbr == "ACN6") return ambisonicACN6; | |||||
if (abbr == "ACN7") return ambisonicACN7; | |||||
if (abbr == "ACN8") return ambisonicACN8; | |||||
if (abbr == "ACN9") return ambisonicACN9; | |||||
if (abbr == "ACN10") return ambisonicACN10; | |||||
if (abbr == "ACN11") return ambisonicACN11; | |||||
if (abbr == "ACN12") return ambisonicACN12; | |||||
if (abbr == "ACN13") return ambisonicACN13; | |||||
if (abbr == "ACN14") return ambisonicACN14; | |||||
if (abbr == "ACN15") return ambisonicACN15; | |||||
if (abbr == "ACN16") return ambisonicACN16; | |||||
if (abbr == "ACN17") return ambisonicACN17; | |||||
if (abbr == "ACN18") return ambisonicACN18; | |||||
if (abbr == "ACN19") return ambisonicACN19; | |||||
if (abbr == "ACN20") return ambisonicACN20; | |||||
if (abbr == "ACN21") return ambisonicACN21; | |||||
if (abbr == "ACN22") return ambisonicACN22; | |||||
if (abbr == "ACN23") return ambisonicACN23; | |||||
if (abbr == "ACN24") return ambisonicACN24; | |||||
if (abbr == "ACN25") return ambisonicACN25; | |||||
if (abbr == "ACN26") return ambisonicACN26; | |||||
if (abbr == "ACN27") return ambisonicACN27; | |||||
if (abbr == "ACN28") return ambisonicACN28; | |||||
if (abbr == "ACN29") return ambisonicACN29; | |||||
if (abbr == "ACN30") return ambisonicACN30; | |||||
if (abbr == "ACN31") return ambisonicACN31; | |||||
if (abbr == "ACN32") return ambisonicACN32; | |||||
if (abbr == "ACN33") return ambisonicACN33; | |||||
if (abbr == "ACN34") return ambisonicACN34; | |||||
if (abbr == "ACN35") return ambisonicACN35; | |||||
if (abbr == "Tsl") return topSideLeft; | |||||
if (abbr == "Tsr") return topSideRight; | |||||
if (abbr == "Bfl") return bottomFrontLeft; | |||||
if (abbr == "Bfc") return bottomFrontCentre; | |||||
if (abbr == "Bfr") return bottomFrontRight; | |||||
if (abbr == "Bsl") return bottomSideLeft; | |||||
if (abbr == "Bsr") return bottomSideRight; | |||||
if (abbr == "Brl") return bottomRearLeft; | |||||
if (abbr == "Brc") return bottomRearCentre; | |||||
if (abbr == "Brr") return bottomRearRight; | |||||
return unknown; | |||||
} | |||||
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", UnitTestCategories::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 |
@@ -0,0 +1,484 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
/** 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. */ | |||||
//============================================================================== | |||||
bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */ | |||||
bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */ | |||||
bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */ | |||||
proximityLeft = 65, /**< Proximity Left (Pl) */ | |||||
proximityRight = 66, /**< Proximity Right (Pr) */ | |||||
bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */ | |||||
bottomSideRight = 68, /**< Bottom Side Right (Bsr) */ | |||||
bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */ | |||||
bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ | |||||
bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ | |||||
//============================================================================== | |||||
discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ | |||||
}; | |||||
/** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||||
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 |
@@ -0,0 +1,596 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fff; | |||||
auto intData = static_cast<char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<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; | |||||
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fff; | |||||
auto intData = static_cast<char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<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; | |||||
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fffff; | |||||
auto 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, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fffff; | |||||
auto 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, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fffffff; | |||||
auto intData = static_cast<char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<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; | |||||
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||||
{ | |||||
auto maxVal = (double) 0x7fffffff; | |||||
auto intData = static_cast<char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<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; | |||||
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||||
{ | |||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
char* d = static_cast<char*> (dest); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<float*> (d) = source[i]; | |||||
#if JUCE_BIG_ENDIAN | |||||
*reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||||
#endif | |||||
d += destBytesPerSample; | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||||
{ | |||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
auto d = static_cast<char*> (dest); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*reinterpret_cast<float*> (d) = source[i]; | |||||
#if JUCE_LITTLE_ENDIAN | |||||
*reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||||
#endif | |||||
d += destBytesPerSample; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fff; | |||||
auto 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 (*reinterpret_cast<const uint16*> (intData)); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fff; | |||||
auto 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 (*reinterpret_cast<const uint16*> (intData)); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffff; | |||||
auto 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* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffff; | |||||
auto 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* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / (float) 0x7fffffff; | |||||
auto intData = static_cast<const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / (float) 0x7fffffff; | |||||
auto intData = static_cast<const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
auto s = static_cast<const char*> (source); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = *reinterpret_cast<const float*> (s); | |||||
#if JUCE_BIG_ENDIAN | |||||
auto d = reinterpret_cast<uint32*> (dest + i); | |||||
*d = ByteOrder::swap (*d); | |||||
#endif | |||||
s += srcBytesPerSample; | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||||
{ | |||||
auto s = static_cast<const char*> (source); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = *reinterpret_cast<const float*> (s); | |||||
#if JUCE_LITTLE_ENDIAN | |||||
auto d = reinterpret_cast<uint32*> (dest + i); | |||||
*d = ByteOrder::swap (*d); | |||||
#endif | |||||
s += srcBytesPerSample; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, 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 (DataFormat sourceFormat, const void* source, float* dest, 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** source, float* dest, int numSamples, int numChannels) | |||||
{ | |||||
for (int chan = 0; chan < numChannels; ++chan) | |||||
{ | |||||
auto i = chan; | |||||
auto src = source [chan]; | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | |||||
dest [i] = src [j]; | |||||
i += numChannels; | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) | |||||
{ | |||||
for (int chan = 0; chan < numChannels; ++chan) | |||||
{ | |||||
auto i = chan; | |||||
auto 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", UnitTestCategories::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 [(size_t) numSamples], | |||||
converted[(size_t) numSamples], | |||||
reversed [(size_t) 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 | |||||
{ | |||||
auto 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 |
@@ -0,0 +1,716 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } | |||||
template <class SampleFormatType> static void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } | |||||
template <class SampleFormatType> static int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } | |||||
template <class SampleFormatType> static void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } | |||||
template <class SourceType, class DestType> static void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } | |||||
enum { isBigEndian = 1 }; | |||||
}; | |||||
class LittleEndian | |||||
{ | |||||
public: | |||||
template <class SampleFormatType> static float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } | |||||
template <class SampleFormatType> static void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } | |||||
template <class SampleFormatType> static int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } | |||||
template <class SampleFormatType> static void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } | |||||
template <class SourceType, class DestType> static 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 + (double) maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); } | |||||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
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 + (double) maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + (double) maxValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int) (((uint8) (*data - 128)) << 24); } | |||||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
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 + (double) maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } | |||||
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 + (double) maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } | |||||
inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } | |||||
inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) ByteOrder::bigEndian24Bit (data)) << 8); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } | |||||
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 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||||
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 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } | |||||
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 + (double) maxValue)))); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <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() = default; | |||||
inline NonInterleaved (const NonInterleaved&) = default; | |||||
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> static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||||
enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||||
}; | |||||
class Interleaved | |||||
{ | |||||
public: | |||||
inline Interleaved() noexcept {} | |||||
inline Interleaved (const Interleaved& other) = default; | |||||
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||||
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||||
template <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 = 1; | |||||
enum { isInterleavedType = 1 }; | |||||
}; | |||||
//============================================================================== | |||||
class NonConst | |||||
{ | |||||
public: | |||||
using VoidType = void; | |||||
static void* toVoidPtr (VoidType* v) noexcept { return v; } | |||||
enum { isConst = 0 }; | |||||
}; | |||||
class Const | |||||
{ | |||||
public: | |||||
using VoidType = const void; | |||||
static 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> ((float) mn * (float) (1.0 / (1.0 + (double) Int32::maxValue)), | |||||
(float) mx * (float) (1.0 / (1.0 + (double) 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() = default; | |||||
/** Converts a sequence of samples from the converter's source format into the dest format. */ | |||||
virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||||
/** 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 |
@@ -0,0 +1,75 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() {} | |||||
AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() {} | |||||
void AudioProcessLoadMeasurer::reset() | |||||
{ | |||||
reset (0, 0); | |||||
} | |||||
void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) | |||||
{ | |||||
cpuUsageMs = 0; | |||||
xruns = 0; | |||||
if (sampleRate > 0.0 && blockSize > 0) | |||||
{ | |||||
msPerBlock = 1000.0 * blockSize / sampleRate; | |||||
timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; | |||||
} | |||||
else | |||||
{ | |||||
msPerBlock = 0; | |||||
timeToCpuScale = 0; | |||||
} | |||||
} | |||||
void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) | |||||
{ | |||||
const double filterAmount = 0.2; | |||||
cpuUsageMs += filterAmount * (milliseconds - cpuUsageMs); | |||||
if (milliseconds > msPerBlock) | |||||
++xruns; | |||||
} | |||||
double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } | |||||
double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } | |||||
int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } | |||||
AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) | |||||
: owner (p), startTime (Time::getMillisecondCounterHiRes()) | |||||
{ | |||||
} | |||||
AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() | |||||
{ | |||||
owner.registerBlockRenderTime (Time::getMillisecondCounterHiRes() - startTime); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,96 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Maintains an ongoing measurement of the proportion of time which is being | |||||
spent inside an audio callback. | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API AudioProcessLoadMeasurer | |||||
{ | |||||
public: | |||||
/** */ | |||||
AudioProcessLoadMeasurer(); | |||||
/** Destructor. */ | |||||
~AudioProcessLoadMeasurer(); | |||||
//============================================================================== | |||||
/** Resets the state. */ | |||||
void reset(); | |||||
/** Resets the counter, in preparation for use with the given sample rate and block size. */ | |||||
void reset (double sampleRate, int blockSize); | |||||
/** Returns the current load as a proportion 0 to 1.0 */ | |||||
double getLoadAsProportion() const; | |||||
/** Returns the current load as a percentage 0 to 100.0 */ | |||||
double getLoadAsPercentage() const; | |||||
/** Returns the number of over- (or under-) runs recorded since the state was reset. */ | |||||
int getXRunCount() const; | |||||
//============================================================================== | |||||
/** This class measures the time between its construction and destruction and | |||||
adds it to an AudioProcessLoadMeasurer. | |||||
e.g. | |||||
@code | |||||
{ | |||||
AudioProcessLoadMeasurer::ScopedTimer timer (myProcessLoadMeasurer); | |||||
myCallback->doTheCallback(); | |||||
} | |||||
@endcode | |||||
@tags{Audio} | |||||
*/ | |||||
struct JUCE_API ScopedTimer | |||||
{ | |||||
ScopedTimer (AudioProcessLoadMeasurer&); | |||||
~ScopedTimer(); | |||||
private: | |||||
AudioProcessLoadMeasurer& owner; | |||||
double startTime; | |||||
JUCE_DECLARE_NON_COPYABLE (ScopedTimer) | |||||
}; | |||||
/** Can be called manually to add the time of a callback to the stats. | |||||
Normally you probably would never call this - it's simpler and more robust to | |||||
use a ScopedTimer to measure the time using an RAII pattern. | |||||
*/ | |||||
void registerBlockRenderTime (double millisecondsTaken); | |||||
private: | |||||
double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0; | |||||
int xruns = 0; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,257 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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,88 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 "buffers/juce_AudioProcessLoadMeasurer.cpp" | |||||
#include "utilities/juce_IIRFilter.cpp" | |||||
#include "utilities/juce_LagrangeInterpolator.cpp" | |||||
#include "utilities/juce_WindowedSincInterpolator.cpp" | |||||
#include "utilities/juce_Interpolators.cpp" | |||||
#include "utilities/juce_SmoothedValue.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" |
@@ -0,0 +1,122 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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.md file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_basics | |||||
vendor: juce | |||||
version: 6.0.0 | |||||
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 "buffers/juce_AudioProcessLoadMeasurer.h" | |||||
#include "utilities/juce_Decibels.h" | |||||
#include "utilities/juce_IIRFilter.h" | |||||
#include "utilities/juce_GenericInterpolator.h" | |||||
#include "utilities/juce_Interpolators.h" | |||||
#include "utilities/juce_SmoothedValue.h" | |||||
#include "utilities/juce_Reverb.h" | |||||
#include "utilities/juce_ADSR.h" | |||||
#include "midi/juce_MidiMessage.h" | |||||
#include "midi/juce_MidiBuffer.h" | |||||
#include "midi/juce_MidiMessageSequence.h" | |||||
#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" |
@@ -0,0 +1,23 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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" |
@@ -0,0 +1,240 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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* d) noexcept | |||||
{ | |||||
return readUnaligned<int32> (d); | |||||
} | |||||
inline uint16 getEventDataSize (const void* d) noexcept | |||||
{ | |||||
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32)); | |||||
} | |||||
inline uint16 getEventTotalSize (const void* d) noexcept | |||||
{ | |||||
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); | |||||
} | |||||
static int findActualEventLength (const uint8* data, int maxBytes) noexcept | |||||
{ | |||||
auto byte = (unsigned int) *data; | |||||
if (byte == 0xf0 || byte == 0xf7) | |||||
{ | |||||
int i = 1; | |||||
while (i < maxBytes) | |||||
if (data[i++] == 0xf7) | |||||
break; | |||||
return i; | |||||
} | |||||
if (byte == 0xff) | |||||
{ | |||||
if (maxBytes == 1) | |||||
return 1; | |||||
int n; | |||||
auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); | |||||
return jmin (maxBytes, n + 2 + bytesLeft); | |||||
} | |||||
if (byte >= 0x80) | |||||
return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); | |||||
return 0; | |||||
} | |||||
static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept | |||||
{ | |||||
while (d < endData && getEventTime (d) <= samplePosition) | |||||
d += getEventTotalSize (d); | |||||
return d; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiBufferIterator& MidiBufferIterator::operator++() noexcept | |||||
{ | |||||
data += sizeof (int32) + sizeof (uint16) + size_t (MidiBufferHelpers::getEventDataSize (data)); | |||||
return *this; | |||||
} | |||||
MidiBufferIterator MidiBufferIterator::operator++ (int) noexcept | |||||
{ | |||||
auto copy = *this; | |||||
++(*this); | |||||
return copy; | |||||
} | |||||
MidiBufferIterator::reference MidiBufferIterator::operator*() const noexcept | |||||
{ | |||||
return { data + sizeof (int32) + sizeof (uint16), | |||||
MidiBufferHelpers::getEventDataSize (data), | |||||
MidiBufferHelpers::getEventTime (data) }; | |||||
} | |||||
//============================================================================== | |||||
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 (int startSample, int numSamples) | |||||
{ | |||||
auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | |||||
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); | |||||
data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); | |||||
} | |||||
void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | |||||
{ | |||||
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||||
} | |||||
void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) | |||||
{ | |||||
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes); | |||||
if (numBytes > 0) | |||||
{ | |||||
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||||
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||||
data.insertMultiple (offset, 0, (int) newItemSize); | |||||
auto* d = data.begin() + offset; | |||||
writeUnaligned<int32> (d, sampleNumber); | |||||
d += sizeof (int32); | |||||
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes)); | |||||
d += sizeof (uint16); | |||||
memcpy (d, newData, (size_t) numBytes); | |||||
} | |||||
} | |||||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||||
int startSample, int numSamples, int sampleDeltaToAdd) | |||||
{ | |||||
for (auto i = otherBuffer.findNextSamplePosition (startSample); i != otherBuffer.cend(); ++i) | |||||
{ | |||||
const auto metadata = *i; | |||||
if (metadata.samplePosition >= startSample + numSamples && numSamples >= 0) | |||||
break; | |||||
addEvent (metadata.data, metadata.numBytes, metadata.samplePosition + sampleDeltaToAdd); | |||||
} | |||||
} | |||||
int MidiBuffer::getNumEvents() const noexcept | |||||
{ | |||||
int n = 0; | |||||
auto end = data.end(); | |||||
for (auto 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; | |||||
auto endData = data.end(); | |||||
for (auto d = data.begin();;) | |||||
{ | |||||
auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||||
if (nextOne >= endData) | |||||
return MidiBufferHelpers::getEventTime (d); | |||||
d = nextOne; | |||||
} | |||||
} | |||||
MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const noexcept | |||||
{ | |||||
return std::find_if (cbegin(), cend(), [&] (const MidiMessageMetadata& metadata) noexcept | |||||
{ | |||||
return metadata.samplePosition >= samplePosition; | |||||
}); | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept | |||||
: buffer (b), iterator (b.data.begin()) | |||||
{ | |||||
} | |||||
MidiBuffer::Iterator::~Iterator() noexcept {} | |||||
void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept | |||||
{ | |||||
iterator = buffer.findNextSamplePosition (samplePosition); | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept | |||||
{ | |||||
if (iterator == buffer.cend()) | |||||
return false; | |||||
const auto metadata = *iterator++; | |||||
midiData = metadata.data; | |||||
numBytes = metadata.numBytes; | |||||
samplePosition = metadata.samplePosition; | |||||
return true; | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||||
{ | |||||
if (iterator == buffer.cend()) | |||||
return false; | |||||
const auto metadata = *iterator++; | |||||
result = metadata.getMessage(); | |||||
samplePosition = metadata.samplePosition; | |||||
return true; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,346 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 view of MIDI message data stored in a contiguous buffer. | |||||
Instances of this class do *not* own the midi data bytes that they point to. | |||||
Instead, they expect the midi data to live in a separate buffer that outlives | |||||
the MidiMessageMetadata instance. | |||||
@tags{Audio} | |||||
*/ | |||||
struct MidiMessageMetadata final | |||||
{ | |||||
MidiMessageMetadata() noexcept = default; | |||||
MidiMessageMetadata (const uint8* dataIn, int numBytesIn, int positionIn) noexcept | |||||
: data (dataIn), numBytes (numBytesIn), samplePosition (positionIn) | |||||
{ | |||||
} | |||||
/** Constructs a new MidiMessage instance from the data that this object is viewing. | |||||
Note that MidiMessage owns its data storage, whereas MidiMessageMetadata does not. | |||||
*/ | |||||
MidiMessage getMessage() const { return MidiMessage (data, numBytes, samplePosition); } | |||||
/** Pointer to the first byte of a MIDI message. */ | |||||
const uint8* data = nullptr; | |||||
/** The number of bytes in the MIDI message. */ | |||||
int numBytes = 0; | |||||
/** The MIDI message's timestamp. */ | |||||
int samplePosition = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
An iterator to move over contiguous raw MIDI data, which Allows iterating | |||||
over a MidiBuffer using C++11 range-for syntax. | |||||
In the following example, we log all three-byte messages in a midi buffer. | |||||
@code | |||||
void processBlock (AudioBuffer<float>&, MidiBuffer& midiBuffer) override | |||||
{ | |||||
for (const MidiMessageMetadata metadata : midiBuffer) | |||||
if (metadata.numBytes == 3) | |||||
Logger::writeToLog (metadata.getMessage().getDescription()); | |||||
} | |||||
@endcode | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiBufferIterator | |||||
{ | |||||
using Ptr = const uint8*; | |||||
public: | |||||
MidiBufferIterator() = default; | |||||
/** Constructs an iterator pointing at the message starting at the byte `dataIn`. | |||||
`dataIn` must point to the start of a valid MIDI message. If it does not, | |||||
calling other member functions on the iterator will result in undefined | |||||
behaviour. | |||||
*/ | |||||
explicit MidiBufferIterator (const uint8* dataIn) noexcept | |||||
: data (dataIn) | |||||
{ | |||||
} | |||||
using difference_type = std::iterator_traits<Ptr>::difference_type; | |||||
using value_type = MidiMessageMetadata; | |||||
using reference = MidiMessageMetadata; | |||||
using pointer = void; | |||||
using iterator_category = std::input_iterator_tag; | |||||
/** Make this iterator point to the next message in the buffer. */ | |||||
MidiBufferIterator& operator++() noexcept; | |||||
/** Create a copy of this object, make this iterator point to the next message in | |||||
the buffer, then return the copy. | |||||
*/ | |||||
MidiBufferIterator operator++ (int) noexcept; | |||||
/** Return true if this iterator points to the same message as another | |||||
iterator instance, otherwise return false. | |||||
*/ | |||||
bool operator== (const MidiBufferIterator& other) const noexcept { return data == other.data; } | |||||
/** Return false if this iterator points to the same message as another | |||||
iterator instance, otherwise returns true. | |||||
*/ | |||||
bool operator!= (const MidiBufferIterator& other) const noexcept { return ! operator== (other); } | |||||
/** Return an instance of MidiMessageMetadata which describes the message to which | |||||
the iterator is currently pointing. | |||||
*/ | |||||
reference operator*() const noexcept; | |||||
private: | |||||
Ptr data = nullptr; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
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 = default; | |||||
/** Creates a MidiBuffer containing a single midi message. */ | |||||
explicit MidiBuffer (const MidiMessage& message) noexcept; | |||||
//============================================================================== | |||||
/** 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 MidiBufferIterator 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 MidiBufferIterator 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 MidiBufferIterator 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); | |||||
/** Get a read-only iterator pointing to the beginning of this buffer. */ | |||||
MidiBufferIterator begin() const noexcept { return cbegin(); } | |||||
/** Get a read-only iterator pointing one past the end of this buffer. */ | |||||
MidiBufferIterator end() const noexcept { return cend(); } | |||||
/** Get a read-only iterator pointing to the beginning of this buffer. */ | |||||
MidiBufferIterator cbegin() const noexcept { return MidiBufferIterator (data.begin()); } | |||||
/** Get a read-only iterator pointing one past the end of this buffer. */ | |||||
MidiBufferIterator cend() const noexcept { return MidiBufferIterator (data.end()); } | |||||
/** Get an iterator pointing to the first event with a timestamp greater-than or | |||||
equal-to `samplePosition`. | |||||
*/ | |||||
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept; | |||||
//============================================================================== | |||||
/** | |||||
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. | |||||
This class has been deprecated in favour of MidiBufferIterator. | |||||
*/ | |||||
JUCE_DEPRECATED (Iterator (const MidiBuffer&) noexcept); | |||||
/** Creates a copy of an iterator. */ | |||||
Iterator (const Iterator&) = default; | |||||
/** 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; | |||||
MidiBufferIterator iterator; | |||||
}; | |||||
/** 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 |
@@ -0,0 +1,446 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 (std::move (other.tracks)), | |||||
timeFormat (other.timeFormat) | |||||
{ | |||||
} | |||||
MidiFile& MidiFile::operator= (MidiFile&& other) | |||||
{ | |||||
tracks = std::move (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, bool createMatchingNoteOffs) | |||||
{ | |||||
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; | |||||
for (;;) | |||||
{ | |||||
auto chunkType = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
auto chunkSize = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
if (chunkSize <= 0 || (size_t) chunkSize > size) | |||||
break; | |||||
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) | |||||
readNextTrack (d, chunkSize, createMatchingNoteOffs); | |||||
if (++track >= expectedTracks) | |||||
break; | |||||
size -= (size_t) chunkSize + 8; | |||||
d += chunkSize; | |||||
} | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||||
{ | |||||
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); | |||||
if (createMatchingNoteOffs) | |||||
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) const | |||||
{ | |||||
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) const | |||||
{ | |||||
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 |
@@ -0,0 +1,197 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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. | |||||
@param sourceStream the source stream | |||||
@param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will | |||||
be automatically added at the end of the file by calling | |||||
MidiMessageSequence::updateMatchedPairs on each track. | |||||
@returns true if the stream was read successfully | |||||
*/ | |||||
bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true); | |||||
/** Writes the midi tracks as a standard midi file. | |||||
The midiFileType value is written as the file's format type, which can be 0, 1 | |||||
or 2 - see the midi file spec for more info about that. | |||||
@param destStream the destination stream | |||||
@param midiFileType the type of midi file | |||||
@returns true if the operation succeeded. | |||||
*/ | |||||
bool writeTo (OutputStream& destStream, int midiFileType = 1) const; | |||||
/** 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, bool); | |||||
bool writeTrack (OutputStream&, const MidiMessageSequence&) const; | |||||
JUCE_LEAK_DETECTOR (MidiFile) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,177 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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] = static_cast<uint16> (noteStates[midiNoteNumber] | (1 << (midiChannel - 1))); | |||||
listeners.call ([&] (Listener& l) { l.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] = static_cast<uint16> (noteStates[midiNoteNumber] & ~(1 << (midiChannel - 1))); | |||||
listeners.call ([&] (Listener& l) { l.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) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (const auto metadata : buffer) | |||||
processNextMidiEvent (metadata.getMessage()); | |||||
if (injectIndirectEvents) | |||||
{ | |||||
const int firstEventToAdd = eventsToAdd.getFirstEventTime(); | |||||
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); | |||||
for (const auto metadata : eventsToAdd) | |||||
{ | |||||
const auto pos = jlimit (0, numSamples - 1, roundToInt ((metadata.samplePosition - firstEventToAdd) * scaleFactor)); | |||||
buffer.addEvent (metadata.getMessage(), startSample + pos); | |||||
} | |||||
} | |||||
eventsToAdd.clear(); | |||||
} | |||||
//============================================================================== | |||||
void MidiKeyboardState::addListener (Listener* listener) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
listeners.add (listener); | |||||
} | |||||
void MidiKeyboardState::removeListener (Listener* listener) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
listeners.remove (listener); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,196 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 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 individual 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 actually 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); | |||||
//============================================================================== | |||||
/** Receives events from a MidiKeyboardState object. */ | |||||
class Listener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
virtual ~Listener() = default; | |||||
//============================================================================== | |||||
/** 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; | |||||
}; | |||||
/** Registers a listener for callbacks when keys go up or down. | |||||
@see removeListener | |||||
*/ | |||||
void addListener (Listener* listener); | |||||
/** Deregisters a listener. | |||||
@see addListener | |||||
*/ | |||||
void removeListener (Listener* listener); | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection lock; | |||||
std::atomic<uint16> noteStates[128]; | |||||
MidiBuffer eventsToAdd; | |||||
ListenerList<Listener> 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) | |||||
}; | |||||
using MidiKeyboardStateListener = MidiKeyboardState::Listener; | |||||
} // namespace juce |
@@ -0,0 +1,952 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 : const_cast<uint8*>(packedData.asBytes); } | |||||
uint8* allocateSpace (int); | |||||
}; | |||||
} // namespace juce |
@@ -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 |
@@ -0,0 +1,11 @@ | |||||
--- modules/juce_audio_basics/midi/juce_MidiMessage.h | |||||
+++ modules/juce_audio_basics/midi/juce_MidiMessage.h | |||||
@@ -945,7 +945,7 @@ private: | |||||
#endif | |||||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||||
- inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } | |||||
+ inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : const_cast<uint8*>(packedData.asBytes); } | |||||
uint8* allocateSpace (int); | |||||
}; | |||||
@@ -0,0 +1,412 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 (std::move (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 (std::move (other.list)) | |||||
{ | |||||
} | |||||
MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept | |||||
{ | |||||
list = std::move (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() noexcept { return list.begin(); } | |||||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); } | |||||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); } | |||||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); } | |||||
double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept | |||||
{ | |||||
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 (std::move (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 UnitTest | |||||
{ | |||||
MidiMessageSequenceTest() | |||||
: UnitTest ("MidiMessageSequence", UnitTestCategories::midi) | |||||
{} | |||||
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 & indices"); | |||||
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 |
@@ -0,0 +1,306 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() noexcept; | |||||
/** Iterator for the list of MidiEventHolders */ | |||||
MidiEventHolder* const* begin() const noexcept; | |||||
/** Iterator for the list of MidiEventHolders */ | |||||
MidiEventHolder** end() noexcept; | |||||
/** Iterator for the list of MidiEventHolders */ | |||||
MidiEventHolder* const* end() const noexcept; | |||||
/** Returns the time of the note-up that matches the note-on at this index. | |||||
If the event at this index isn't a note-on, it'll just return 0. | |||||
@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 |
@@ -0,0 +1,380 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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) | |||||
{ | |||||
MidiRPNMessage result = MidiRPNMessage(); | |||||
MidiRPNDetector detector; | |||||
for (const auto metadata : midiBuffer) | |||||
{ | |||||
const auto midiMessage = metadata.getMessage(); | |||||
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 | |||||
} // namespace juce |
@@ -0,0 +1,154 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 precision (a single MIDI byte). | |||||
*/ | |||||
static MidiBuffer generate (int channel, | |||||
int parameterNumber, | |||||
int value, | |||||
bool isNRPN = false, | |||||
bool use14BitValue = true); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,413 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 maintains 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) const 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; | |||||
/** Returns true if the given MIDI channel (1-16) is used by any of the | |||||
MPEInstrument's MPE zones; false otherwise. | |||||
When in legacy mode, this will return true if the given channel is | |||||
contained in the current legacy mode channel range; false otherwise. | |||||
*/ | |||||
bool isUsingChannel (int midiChannel) const noexcept; | |||||
//============================================================================== | |||||
/** The MPE note tracking mode. In case there is more than one note playing | |||||
simultaneously on the same MIDI channel, this determines which of these | |||||
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 poly-aftertouch change for a given note number. | |||||
The change will be broadcast to all notes sharing the channel and note | |||||
number of the change message. | |||||
*/ | |||||
virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value); | |||||
/** Request a sustain pedal press or release. | |||||
If midiChannel is a zone's master channel, this will act on all notes in | |||||
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() = default; | |||||
/** Implement this callback to be informed whenever a new expressive MIDI | |||||
note is triggered. | |||||
*/ | |||||
virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); } | |||||
/** Implement this callback to be informed whenever a currently playing | |||||
MPE note's pressure value changes. | |||||
*/ | |||||
virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||||
/** 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) { ignoreUnused (changedNote); } | |||||
/** Implement this callback to be informed whenever a currently playing | |||||
MPE note's timbre value changes. | |||||
*/ | |||||
virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||||
/** Implement this callback to be informed whether a currently playing | |||||
MPE note's key state (whether the key is down and/or the note is | |||||
sustained) has changed. | |||||
Note: If the key state changes to MPENote::off, noteReleased is | |||||
called instead. | |||||
*/ | |||||
virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||||
/** Implement this callback to be informed whenever an MPE note | |||||
is released (either by a note-off message, or by a sustain/sostenuto | |||||
pedal release for a note that already received a note-off), | |||||
and should therefore stop playing. | |||||
*/ | |||||
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } | |||||
}; | |||||
//============================================================================== | |||||
/** Adds a listener. */ | |||||
void addListener (Listener* listenerToAdd); | |||||
/** Removes a listener. */ | |||||
void removeListener (Listener* listenerToRemove); | |||||
//============================================================================== | |||||
/** 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 | |||||
{ | |||||
TrackingMode trackingMode = lastNotePlayedOnChannel; | |||||
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 processMidiAfterTouchMessage (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 |
@@ -0,0 +1,238 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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; | |||||
for (const auto metadata : midiBuffer) | |||||
{ | |||||
const uint8* data = metadata.data; | |||||
std::size_t dataSize = (std::size_t) metadata.numBytes; | |||||
if (pos + dataSize > maxBytes) | |||||
return; | |||||
std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); | |||||
pos += dataSize; | |||||
} | |||||
} | |||||
}; | |||||
static MPEMessagesTests MPEMessagesUnitTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,116 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 layout passed in. This will first clear the current lower and upper | |||||
zones, then then set the zones contained in the passed-in zone layout, and set their | |||||
per-note and master pitchbend ranges to their current values. | |||||
*/ | |||||
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 |
@@ -0,0 +1,127 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
//============================================================================== | |||||
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 | |||||
} // namespace juce |
@@ -0,0 +1,184 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() }; | |||||
/** Initial 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 |
@@ -0,0 +1,338 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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->noteOnTime = lastNoteOnCounter++; | |||||
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->noteOnTime < b->noteOnTime; } | |||||
}; | |||||
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) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
// first turn off all voices (it's more efficient to do this immediately | |||||
// rather than to go through the MPEInstrument for this). | |||||
for (auto* voice : voices) | |||||
voice->noteStopped (allowTailOff); | |||||
} | |||||
// finally make sure the MPE Instrument also doesn't have any notes anymore. | |||||
instrument->releaseAllNotes(); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->isActive()) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
} | |||||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->isActive()) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,312 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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* instrumentToUse); | |||||
/** Destructor. */ | |||||
~MPESynthesiser() override; | |||||
//============================================================================== | |||||
/** 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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. (double-precision version) | |||||
Override this method if you need to do more work to render your audio. | |||||
*/ | |||||
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; | |||||
uint32 lastNoteOnCounter = 0; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,376 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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); | |||||
const ScopedLock sl (noteStateLock); | |||||
auto prevSample = startSample; | |||||
const auto endSample = startSample + numSamples; | |||||
for (auto it = inputMidi.findNextSamplePosition (startSample); it != inputMidi.cend(); ++it) | |||||
{ | |||||
const auto metadata = *it; | |||||
if (metadata.samplePosition >= endSample) | |||||
break; | |||||
const auto smallBlockAllowed = (prevSample == startSample && ! subBlockSubdivisionIsStrict); | |||||
const auto thisBlockSize = smallBlockAllowed ? 1 : minimumSubBlockSize; | |||||
if (metadata.samplePosition >= prevSample + thisBlockSize) | |||||
{ | |||||
renderNextSubBlock (outputAudio, prevSample, metadata.samplePosition - prevSample); | |||||
prevSample = metadata.samplePosition; | |||||
} | |||||
handleMidiEvent (metadata.getMessage()); | |||||
} | |||||
if (prevSample < endSample) | |||||
renderNextSubBlock (outputAudio, prevSample, endSample - prevSample); | |||||
} | |||||
// 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; | |||||
} | |||||
#if JUCE_UNIT_TESTS | |||||
namespace | |||||
{ | |||||
class MpeSynthesiserBaseTests : public UnitTest | |||||
{ | |||||
enum class CallbackKind { process, midi }; | |||||
struct StartAndLength | |||||
{ | |||||
StartAndLength (int s, int l) : start (s), length (l) {} | |||||
int start = 0; | |||||
int length = 0; | |||||
std::tuple<const int&, const int&> tie() const noexcept { return std::tie (start, length); } | |||||
bool operator== (const StartAndLength& other) const noexcept { return tie() == other.tie(); } | |||||
bool operator!= (const StartAndLength& other) const noexcept { return tie() != other.tie(); } | |||||
bool operator< (const StartAndLength& other) const noexcept { return tie() < other.tie(); } | |||||
}; | |||||
struct Events | |||||
{ | |||||
std::vector<StartAndLength> blocks; | |||||
std::vector<MidiMessage> messages; | |||||
std::vector<CallbackKind> order; | |||||
}; | |||||
class MockSynthesiser : public MPESynthesiserBase | |||||
{ | |||||
public: | |||||
Events events; | |||||
void handleMidiEvent (const MidiMessage& m) override | |||||
{ | |||||
events.messages.emplace_back (m); | |||||
events.order.emplace_back (CallbackKind::midi); | |||||
} | |||||
private: | |||||
using MPESynthesiserBase::renderNextSubBlock; | |||||
void renderNextSubBlock (AudioBuffer<float>&, | |||||
int startSample, | |||||
int numSamples) override | |||||
{ | |||||
events.blocks.push_back ({ startSample, numSamples }); | |||||
events.order.emplace_back (CallbackKind::process); | |||||
} | |||||
}; | |||||
static MidiBuffer makeTestBuffer (const int bufferLength) | |||||
{ | |||||
MidiBuffer result; | |||||
for (int i = 0; i != bufferLength; ++i) | |||||
result.addEvent ({}, i); | |||||
return result; | |||||
} | |||||
public: | |||||
MpeSynthesiserBaseTests() | |||||
: UnitTest ("MPE Synthesiser Base", UnitTestCategories::midi) {} | |||||
void runTest() override | |||||
{ | |||||
const auto sumBlockLengths = [] (const std::vector<StartAndLength>& b) | |||||
{ | |||||
const auto addBlock = [] (int acc, const StartAndLength& info) { return acc + info.length; }; | |||||
return std::accumulate (b.begin(), b.end(), 0, addBlock); | |||||
}; | |||||
beginTest ("Rendering sparse subblocks works"); | |||||
{ | |||||
const int blockSize = 512; | |||||
const auto midi = [&] { MidiBuffer b; b.addEvent ({}, blockSize / 2); return b; }(); | |||||
AudioBuffer<float> audio (1, blockSize); | |||||
const auto processEvents = [&] (int start, int length) | |||||
{ | |||||
MockSynthesiser synth; | |||||
synth.setMinimumRenderingSubdivisionSize (1, false); | |||||
synth.setCurrentPlaybackSampleRate (44100); | |||||
synth.renderNextBlock (audio, midi, start, length); | |||||
return synth.events; | |||||
}; | |||||
{ | |||||
const auto e = processEvents (0, blockSize); | |||||
expect (e.blocks.size() == 2); | |||||
expect (e.messages.size() == 1); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == blockSize); | |||||
expect (e.order == std::vector<CallbackKind> { CallbackKind::process, | |||||
CallbackKind::midi, | |||||
CallbackKind::process }); | |||||
} | |||||
} | |||||
beginTest ("Rendering subblocks processes only contained midi events"); | |||||
{ | |||||
const int blockSize = 512; | |||||
const auto midi = makeTestBuffer (blockSize); | |||||
AudioBuffer<float> audio (1, blockSize); | |||||
const auto processEvents = [&] (int start, int length) | |||||
{ | |||||
MockSynthesiser synth; | |||||
synth.setMinimumRenderingSubdivisionSize (1, false); | |||||
synth.setCurrentPlaybackSampleRate (44100); | |||||
synth.renderNextBlock (audio, midi, start, length); | |||||
return synth.events; | |||||
}; | |||||
{ | |||||
const int subBlockLength = 0; | |||||
const auto e = processEvents (0, subBlockLength); | |||||
expect (e.blocks.size() == 0); | |||||
expect (e.messages.size() == 0); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||||
} | |||||
{ | |||||
const int subBlockLength = 0; | |||||
const auto e = processEvents (1, subBlockLength); | |||||
expect (e.blocks.size() == 0); | |||||
expect (e.messages.size() == 0); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||||
} | |||||
{ | |||||
const int subBlockLength = 1; | |||||
const auto e = processEvents (1, subBlockLength); | |||||
expect (e.blocks.size() == 1); | |||||
expect (e.messages.size() == 1); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||||
expect (e.order == std::vector<CallbackKind> { CallbackKind::midi, | |||||
CallbackKind::process }); | |||||
} | |||||
{ | |||||
const auto e = processEvents (0, blockSize); | |||||
expect (e.blocks.size() == blockSize); | |||||
expect (e.messages.size() == blockSize); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == blockSize); | |||||
expect (e.order.front() == CallbackKind::midi); | |||||
} | |||||
} | |||||
beginTest ("Subblocks respect their minimum size"); | |||||
{ | |||||
const int blockSize = 512; | |||||
const auto midi = makeTestBuffer (blockSize); | |||||
AudioBuffer<float> audio (1, blockSize); | |||||
const auto blockLengthsAreValid = [] (const std::vector<StartAndLength>& info, int minLength, bool strict) | |||||
{ | |||||
if (info.size() <= 1) | |||||
return true; | |||||
const auto lengthIsValid = [&] (const StartAndLength& s) { return minLength <= s.length; }; | |||||
const auto begin = strict ? info.begin() : std::next (info.begin()); | |||||
// The final block is allowed to be shorter than the minLength | |||||
return std::all_of (begin, std::prev (info.end()), lengthIsValid); | |||||
}; | |||||
for (auto strict : { false, true }) | |||||
{ | |||||
for (auto subblockSize : { 1, 16, 32, 64, 1024 }) | |||||
{ | |||||
MockSynthesiser synth; | |||||
synth.setMinimumRenderingSubdivisionSize (subblockSize, strict); | |||||
synth.setCurrentPlaybackSampleRate (44100); | |||||
synth.renderNextBlock (audio, midi, 0, blockSize); | |||||
const auto& e = synth.events; | |||||
expectWithinAbsoluteError (float (e.blocks.size()), | |||||
std::ceil ((float) blockSize / (float) subblockSize), | |||||
1.0f); | |||||
expect (e.messages.size() == blockSize); | |||||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||||
expect (sumBlockLengths (e.blocks) == blockSize); | |||||
expect (blockLengthsAreValid (e.blocks, subblockSize, strict)); | |||||
} | |||||
} | |||||
{ | |||||
MockSynthesiser synth; | |||||
synth.setMinimumRenderingSubdivisionSize (32, true); | |||||
synth.setCurrentPlaybackSampleRate (44100); | |||||
synth.renderNextBlock (audio, MidiBuffer{}, 0, 16); | |||||
expect (synth.events.blocks == std::vector<StartAndLength> { { 0, 16 } }); | |||||
expect (synth.events.order == std::vector<CallbackKind> { CallbackKind::process }); | |||||
expect (synth.events.messages.empty()); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
MpeSynthesiserBaseTests mpeSynthesiserBaseTests; | |||||
} | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,215 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
renderNextSubBlock 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. | |||||
@param outputAudio Buffer into which audio will be rendered | |||||
@param inputMidi MIDI events to process | |||||
@param startSample The first sample to process in both buffers | |||||
@param numSamples The number of samples to process | |||||
*/ | |||||
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 |
@@ -0,0 +1,50 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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; | |||||
} | |||||
void MPESynthesiserVoice::clearCurrentNote() noexcept | |||||
{ | |||||
currentlyPlayingNote = MPENote(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,191 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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; } | |||||
/** This will be set to an incrementing counter value in MPESynthesiser::startVoice() | |||||
and can be used to determine the order in which voices started. | |||||
*/ | |||||
uint32 noteOnTime = 0; | |||||
protected: | |||||
//============================================================================== | |||||
/** 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; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,494 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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, int midiChannel) | |||||
{ | |||||
const auto removeNote = [] (MidiChannel& ch, int noteNum) | |||||
{ | |||||
if (ch.notes.removeAllInstancesOf (noteNum) > 0) | |||||
{ | |||||
ch.lastNotePlayed = noteNum; | |||||
return true; | |||||
} | |||||
return false; | |||||
}; | |||||
if (midiChannel >= 0 && midiChannel < 17) | |||||
{ | |||||
removeNote (midiChannels[midiChannel], noteNumber); | |||||
return; | |||||
} | |||||
for (auto& ch : midiChannels) | |||||
{ | |||||
if (removeNote (ch, noteNumber)) | |||||
return; | |||||
} | |||||
} | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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 |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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. | |||||
You can specify the channel number the note off happened on. If you don't, it will | |||||
look through all channels to find the registered midi note matching the given note number. | |||||
*/ | |||||
void noteOff (int noteNumber, int midiChannel = -1); | |||||
/** 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 |
@@ -0,0 +1,173 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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 | |||||
} // namespace juce |
@@ -0,0 +1,97 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,386 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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) | |||||
{ | |||||
for (const auto metadata : buffer) | |||||
processNextMidiEvent (metadata.getMessage()); | |||||
} | |||||
//============================================================================== | |||||
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", UnitTestCategories::midi) | |||||
{} | |||||
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 | |||||
} // namespace juce |
@@ -0,0 +1,225 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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) = default; | |||||
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 isUsing (int channel) const noexcept | |||||
{ | |||||
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); | |||||
} | |||||
bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone | |||||
&& numMemberChannels == other.numMemberChannels | |||||
&& perNotePitchbendRange == other.perNotePitchbendRange | |||||
&& 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() = default; | |||||
/** 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 |
@@ -0,0 +1,330 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,179 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
/** 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() = default; | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~AudioSource() = default; | |||||
//============================================================================== | |||||
/** Tells the source to prepare for playing. | |||||
An AudioSource has two states: prepared and unprepared. | |||||
The prepareToPlay() method is guaranteed to be called at least once on an 'unprepared' | |||||
source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). | |||||
This callback allows the source to initialise any resources it might need when playing. | |||||
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 |
@@ -0,0 +1,315 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,119 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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) 2020 - Raw Material Software Limited | |||||
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); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
std::unique_ptr<XmlElement> ChannelRemappingAudioSource::createXml() const | |||||
{ | |||||
auto e = std::make_unique<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) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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 | |||||
*/ | |||||
std::unique_ptr<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) 2020 - Raw Material Software Limited | |||||
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) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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 |
@@ -0,0 +1,100 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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) | |||||
: isCurrentlyLooping (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; | |||||
int i; | |||||
for (i = position; (i < n || isCurrentlyLooping) && (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); | |||||
position = (i % n); | |||||
} | |||||
//============================================================================== | |||||
void MemoryAudioSource::setNextReadPosition (int64 newPosition) | |||||
{ | |||||
position = (int) newPosition; | |||||
} | |||||
int64 MemoryAudioSource::getNextReadPosition() const | |||||
{ | |||||
return position; | |||||
} | |||||
int64 MemoryAudioSource::getTotalLength() const | |||||
{ | |||||
return buffer.getNumSamples(); | |||||
} | |||||
//============================================================================== | |||||
bool MemoryAudioSource::isLooping() const | |||||
{ | |||||
return isCurrentlyLooping; | |||||
} | |||||
void MemoryAudioSource::setLooping (bool shouldLoop) | |||||
{ | |||||
isCurrentlyLooping = shouldLoop; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,82 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 PositionableAudioSource | |||||
{ | |||||
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; | |||||
//============================================================================== | |||||
/** Implementation of the PositionableAudioSource method. */ | |||||
void setNextReadPosition (int64 newPosition) override; | |||||
/** Implementation of the PositionableAudioSource method. */ | |||||
int64 getNextReadPosition() const override; | |||||
/** Implementation of the PositionableAudioSource method. */ | |||||
int64 getTotalLength() const override; | |||||
//============================================================================== | |||||
/** Implementation of the PositionableAudioSource method. */ | |||||
bool isLooping() const override; | |||||
/** Implementation of the PositionableAudioSource method. */ | |||||
void setLooping (bool shouldLoop) override; | |||||
private: | |||||
//============================================================================== | |||||
AudioBuffer<float> buffer; | |||||
int position = 0; | |||||
bool isCurrentlyLooping; | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,158 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,99 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
public: | |||||
/** Destructor */ | |||||
~PositionableAudioSource() override = default; | |||||
//============================================================================== | |||||
/** 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 |
@@ -0,0 +1,265 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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), | |||||
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() | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
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) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
double localRatio; | |||||
{ | |||||
const SpinLock::ScopedLockType ratioSl (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 |
@@ -0,0 +1,106 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
/** 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 = 1.0, lastRatio = 1.0; | |||||
AudioBuffer<float> buffer; | |||||
int bufferPos = 0, sampsInBuffer = 0; | |||||
double subSampleOffset = 0.0; | |||||
double coefficients[6]; | |||||
SpinLock ratioLock; | |||||
CriticalSection callbackLock; | |||||
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 |
@@ -0,0 +1,83 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,74 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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) 2020 - Raw Material Software Limited | |||||
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) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** 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 |
@@ -0,0 +1,585 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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(); | |||||
auto midiIterator = midiData.findNextSamplePosition (startSample); | |||||
bool firstEvent = true; | |||||
const ScopedLock sl (lock); | |||||
for (; numSamples > 0; ++midiIterator) | |||||
{ | |||||
if (midiIterator == midiData.cend()) | |||||
{ | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
return; | |||||
} | |||||
const auto metadata = *midiIterator; | |||||
const int samplesToNextMidiMessage = metadata.samplePosition - startSample; | |||||
if (samplesToNextMidiMessage >= numSamples) | |||||
{ | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
handleMidiEvent (metadata.getMessage()); | |||||
break; | |||||
} | |||||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
{ | |||||
handleMidiEvent (metadata.getMessage()); | |||||
continue; | |||||
} | |||||
firstEvent = false; | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
handleMidiEvent (metadata.getMessage()); | |||||
startSample += samplesToNextMidiMessage; | |||||
numSamples -= samplesToNextMidiMessage; | |||||
} | |||||
std::for_each (midiIterator, | |||||
midiData.cend(), | |||||
[&] (const MidiMessageMetadata& meta) { handleMidiEvent (meta.getMessage()); }); | |||||
} | |||||
// 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::renderNextBlock (AudioBuffer<float>& outputAudio, const MidiBuffer& inputMidi, | |||||
int startSample, int numSamples) | |||||
{ | |||||
processNextBlock (outputAudio, inputMidi, startSample, numSamples); | |||||
} | |||||
void Synthesiser::renderNextBlock (AudioBuffer<double>& outputAudio, const MidiBuffer& inputMidi, | |||||
int startSample, int numSamples) | |||||
{ | |||||
processNextBlock (outputAudio, inputMidi, startSample, numSamples); | |||||
} | |||||
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() == nullptr)); | |||||
} | |||||
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 (auto 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 |
@@ -0,0 +1,645 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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. */ | |||||
~SynthesiserSound() override; | |||||
//============================================================================== | |||||
/** 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; | |||||
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::Ptr 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. | |||||
*/ | |||||
void renderNextBlock (AudioBuffer<float>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
void renderNextBlock (AudioBuffer<double>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
Subclasses may need to know this so that they can pitch things correctly. | |||||
*/ | |||||
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: | |||||
//============================================================================== | |||||
double sampleRate = 0; | |||||
uint32 lastNoteOnCounter = 0; | |||||
int minimumSubBlockSize = 32; | |||||
bool subBlockSubdivisionIsStrict = false; | |||||
bool shouldStealNotes = true; | |||||
BigInteger sustainPedalsDown; | |||||
template <typename floatType> | |||||
void processNextBlock (AudioBuffer<floatType>&, const MidiBuffer&, int startSample, int numSamples); | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// Note the new parameters for these methods. | |||||
virtual int findFreeVoice (const bool) const { return 0; } | |||||
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 |
@@ -0,0 +1,248 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A very simple ADSR envelope class. | |||||
To use it, call setSampleRate() with the current sample rate and give it some parameters | |||||
with setParameters() then call getNextSample() to get the envelope value to be applied | |||||
to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer. | |||||
@tags{Audio} | |||||
*/ | |||||
class ADSR | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
ADSR() | |||||
{ | |||||
setSampleRate (44100.0); | |||||
setParameters ({}); | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Holds the parameters being used by an ADSR object. | |||||
@tags{Audio} | |||||
*/ | |||||
struct Parameters | |||||
{ | |||||
/** Attack time in seconds. */ | |||||
float attack = 0.1f; | |||||
/** Decay time in seconds. */ | |||||
float decay = 0.1f; | |||||
/** Sustain level. */ | |||||
float sustain = 1.0f; | |||||
/** Release time in seconds. */ | |||||
float release = 0.1f; | |||||
}; | |||||
/** Sets the parameters that will be used by an ADSR object. | |||||
You must have called setSampleRate() with the correct sample rate before | |||||
this otherwise the values may be incorrect! | |||||
@see getParameters | |||||
*/ | |||||
void setParameters (const Parameters& newParameters) | |||||
{ | |||||
currentParameters = newParameters; | |||||
sustainLevel = newParameters.sustain; | |||||
calculateRates (newParameters); | |||||
if (currentState != State::idle) | |||||
checkCurrentState(); | |||||
} | |||||
/** Returns the parameters currently being used by an ADSR object. | |||||
@see setParameters | |||||
*/ | |||||
const Parameters& getParameters() const { return currentParameters; } | |||||
/** Returns true if the envelope is in its attack, decay, sustain or release stage. */ | |||||
bool isActive() const noexcept { return currentState != State::idle; } | |||||
//============================================================================== | |||||
/** Sets the sample rate that will be used for the envelope. | |||||
This must be called before the getNextSample() or setParameters() methods. | |||||
*/ | |||||
void setSampleRate (double sampleRate) | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
sr = sampleRate; | |||||
} | |||||
//============================================================================== | |||||
/** Resets the envelope to an idle state. */ | |||||
void reset() | |||||
{ | |||||
envelopeVal = 0.0f; | |||||
currentState = State::idle; | |||||
} | |||||
/** Starts the attack phase of the envelope. */ | |||||
void noteOn() | |||||
{ | |||||
if (attackRate > 0.0f) | |||||
{ | |||||
currentState = State::attack; | |||||
} | |||||
else if (decayRate > 0.0f) | |||||
{ | |||||
envelopeVal = 1.0f; | |||||
currentState = State::decay; | |||||
} | |||||
else | |||||
{ | |||||
currentState = State::sustain; | |||||
} | |||||
} | |||||
/** Starts the release phase of the envelope. */ | |||||
void noteOff() | |||||
{ | |||||
if (currentState != State::idle) | |||||
{ | |||||
if (currentParameters.release > 0.0f) | |||||
{ | |||||
releaseRate = static_cast<float> (envelopeVal / (currentParameters.release * sr)); | |||||
currentState = State::release; | |||||
} | |||||
else | |||||
{ | |||||
reset(); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Returns the next sample value for an ADSR object. | |||||
@see applyEnvelopeToBuffer | |||||
*/ | |||||
float getNextSample() | |||||
{ | |||||
if (currentState == State::idle) | |||||
return 0.0f; | |||||
if (currentState == State::attack) | |||||
{ | |||||
envelopeVal += attackRate; | |||||
if (envelopeVal >= 1.0f) | |||||
{ | |||||
envelopeVal = 1.0f; | |||||
if (decayRate > 0.0f) | |||||
currentState = State::decay; | |||||
else | |||||
currentState = State::sustain; | |||||
} | |||||
} | |||||
else if (currentState == State::decay) | |||||
{ | |||||
envelopeVal -= decayRate; | |||||
if (envelopeVal <= sustainLevel) | |||||
{ | |||||
envelopeVal = sustainLevel; | |||||
currentState = State::sustain; | |||||
} | |||||
} | |||||
else if (currentState == State::sustain) | |||||
{ | |||||
envelopeVal = sustainLevel; | |||||
} | |||||
else if (currentState == State::release) | |||||
{ | |||||
envelopeVal -= releaseRate; | |||||
if (envelopeVal <= 0.0f) | |||||
reset(); | |||||
} | |||||
return envelopeVal; | |||||
} | |||||
/** This method will conveniently apply the next numSamples number of envelope values | |||||
to an AudioBuffer. | |||||
@see getNextSample | |||||
*/ | |||||
template<typename FloatType> | |||||
void applyEnvelopeToBuffer (AudioBuffer<FloatType>& buffer, int startSample, int numSamples) | |||||
{ | |||||
jassert (startSample + numSamples <= buffer.getNumSamples()); | |||||
auto numChannels = buffer.getNumChannels(); | |||||
while (--numSamples >= 0) | |||||
{ | |||||
auto env = getNextSample(); | |||||
for (int i = 0; i < numChannels; ++i) | |||||
buffer.getWritePointer (i)[startSample] *= env; | |||||
++startSample; | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void calculateRates (const Parameters& parameters) | |||||
{ | |||||
// need to call setSampleRate() first! | |||||
jassert (sr > 0.0); | |||||
attackRate = (parameters.attack > 0.0f ? static_cast<float> (1.0f / (parameters.attack * sr)) : -1.0f); | |||||
decayRate = (parameters.decay > 0.0f ? static_cast<float> ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f); | |||||
} | |||||
void checkCurrentState() | |||||
{ | |||||
if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain; | |||||
else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain; | |||||
else if (currentState == State::release && releaseRate <= 0.0f) reset(); | |||||
} | |||||
//============================================================================== | |||||
enum class State { idle, attack, decay, sustain, release }; | |||||
State currentState = State::idle; | |||||
Parameters currentParameters; | |||||
double sr = 0.0; | |||||
float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,112 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,500 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 6 End-User License | |||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
End User License Agreement: www.juce.com/juce-6-licence | |||||
Privacy Policy: www.juce.com/juce-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 interpolator base class for resampling streams of floats. | |||||
Note that the resamplers are 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 interpolator object. | |||||
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator, | |||||
LinearInterpolator, ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
template <class InterpolatorTraits, int memorySize> | |||||
class JUCE_API GenericInterpolator | |||||
{ | |||||
public: | |||||
GenericInterpolator() noexcept { reset(); } | |||||
GenericInterpolator (GenericInterpolator&&) noexcept = default; | |||||
GenericInterpolator& operator= (GenericInterpolator&&) noexcept = default; | |||||
/** Returns the latency of the interpolation algorithm in isolation. | |||||
In the context of resampling the total latency of a process using | |||||
the interpolator is the base latency divided by the speed ratio. | |||||
*/ | |||||
static constexpr float getBaseLatency() noexcept | |||||
{ | |||||
return InterpolatorTraits::algorithmicLatency; | |||||
} | |||||
/** Resets the state of the interpolator. | |||||
Call this when there's a break in the continuity of the input data stream. | |||||
*/ | |||||
void reset() noexcept | |||||
{ | |||||
indexBuffer = 0; | |||||
subSamplePos = 1.0; | |||||
std::fill (std::begin (lastInputSamples), std::end (lastInputSamples), 0.0f); | |||||
} | |||||
/** 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 | |||||
{ | |||||
return interpolate (speedRatio, inputSamples, outputSamples, numOutputSamplesToProduce); | |||||
} | |||||
/** 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 numInputSamplesAvailable 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 numInputSamplesAvailable, | |||||
int wrapAround) noexcept | |||||
{ | |||||
return interpolate (speedRatio, inputSamples, outputSamples, | |||||
numOutputSamplesToProduce, numInputSamplesAvailable, wrapAround); | |||||
} | |||||
/** 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 | |||||
{ | |||||
return interpolateAdding (speedRatio, inputSamples, outputSamples, numOutputSamplesToProduce, gain); | |||||
} | |||||
/** 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 numInputSamplesAvailable 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 numInputSamplesAvailable, | |||||
int wrapAround, | |||||
float gain) noexcept | |||||
{ | |||||
return interpolateAdding (speedRatio, inputSamples, outputSamples, | |||||
numOutputSamplesToProduce, numInputSamplesAvailable, wrapAround, gain); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
forcedinline void pushInterpolationSample (float newValue) noexcept | |||||
{ | |||||
lastInputSamples[indexBuffer] = newValue; | |||||
if (++indexBuffer == memorySize) | |||||
indexBuffer = 0; | |||||
} | |||||
forcedinline void pushInterpolationSamples (const float* input, | |||||
int numOutputSamplesToProduce) noexcept | |||||
{ | |||||
if (numOutputSamplesToProduce >= memorySize) | |||||
{ | |||||
const auto* const offsetInput = input + (numOutputSamplesToProduce - memorySize); | |||||
for (int i = 0; i < memorySize; ++i) | |||||
pushInterpolationSample (offsetInput[i]); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numOutputSamplesToProduce; ++i) | |||||
pushInterpolationSample (input[i]); | |||||
} | |||||
} | |||||
forcedinline void pushInterpolationSamples (const float* input, | |||||
int numOutputSamplesToProduce, | |||||
int numInputSamplesAvailable, | |||||
int wrapAround) noexcept | |||||
{ | |||||
if (numOutputSamplesToProduce >= memorySize) | |||||
{ | |||||
if (numInputSamplesAvailable >= memorySize) | |||||
{ | |||||
pushInterpolationSamples (input, | |||||
numOutputSamplesToProduce); | |||||
} | |||||
else | |||||
{ | |||||
pushInterpolationSamples (input + ((numOutputSamplesToProduce - numInputSamplesAvailable) - 1), | |||||
numInputSamplesAvailable); | |||||
if (wrapAround > 0) | |||||
{ | |||||
numOutputSamplesToProduce -= wrapAround; | |||||
pushInterpolationSamples (input + ((numOutputSamplesToProduce - (memorySize - numInputSamplesAvailable)) - 1), | |||||
memorySize - numInputSamplesAvailable); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = numInputSamplesAvailable; i < memorySize; ++i) | |||||
pushInterpolationSample (0.0f); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (numOutputSamplesToProduce > numInputSamplesAvailable) | |||||
{ | |||||
for (int i = 0; i < numInputSamplesAvailable; ++i) | |||||
pushInterpolationSample (input[i]); | |||||
const auto extraSamples = numOutputSamplesToProduce - numInputSamplesAvailable; | |||||
if (wrapAround > 0) | |||||
{ | |||||
const auto* const offsetInput = input + (numInputSamplesAvailable - wrapAround); | |||||
for (int i = 0; i < extraSamples; ++i) | |||||
pushInterpolationSample (offsetInput[i]); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < extraSamples; ++i) | |||||
pushInterpolationSample (0.0f); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numOutputSamplesToProduce; ++i) | |||||
pushInterpolationSample (input[i]); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
int interpolate (double speedRatio, | |||||
const float* input, | |||||
float* output, | |||||
int numOutputSamplesToProduce) noexcept | |||||
{ | |||||
auto pos = subSamplePos; | |||||
int numUsed = 0; | |||||
while (numOutputSamplesToProduce > 0) | |||||
{ | |||||
while (pos >= 1.0) | |||||
{ | |||||
pushInterpolationSample (input[numUsed++]); | |||||
pos -= 1.0; | |||||
} | |||||
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer); | |||||
pos += speedRatio; | |||||
--numOutputSamplesToProduce; | |||||
} | |||||
subSamplePos = pos; | |||||
return numUsed; | |||||
} | |||||
int interpolate (double speedRatio, | |||||
const float* input, float* output, | |||||
int numOutputSamplesToProduce, | |||||
int numInputSamplesAvailable, | |||||
int wrap) noexcept | |||||
{ | |||||
auto originalIn = input; | |||||
auto pos = subSamplePos; | |||||
bool exceeded = false; | |||||
if (speedRatio < 1.0) | |||||
{ | |||||
for (int i = numOutputSamplesToProduce; --i >= 0;) | |||||
{ | |||||
if (pos >= 1.0) | |||||
{ | |||||
if (exceeded) | |||||
{ | |||||
pushInterpolationSample (0.0f); | |||||
} | |||||
else | |||||
{ | |||||
pushInterpolationSample (*input++); | |||||
if (--numInputSamplesAvailable <= 0) | |||||
{ | |||||
if (wrap > 0) | |||||
{ | |||||
input -= wrap; | |||||
numInputSamplesAvailable += wrap; | |||||
} | |||||
else | |||||
{ | |||||
exceeded = true; | |||||
} | |||||
} | |||||
} | |||||
pos -= 1.0; | |||||
} | |||||
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer); | |||||
pos += speedRatio; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = numOutputSamplesToProduce; --i >= 0;) | |||||
{ | |||||
while (pos < speedRatio) | |||||
{ | |||||
if (exceeded) | |||||
{ | |||||
pushInterpolationSample (0); | |||||
} | |||||
else | |||||
{ | |||||
pushInterpolationSample (*input++); | |||||
if (--numInputSamplesAvailable <= 0) | |||||
{ | |||||
if (wrap > 0) | |||||
{ | |||||
input -= wrap; | |||||
numInputSamplesAvailable += wrap; | |||||
} | |||||
else | |||||
{ | |||||
exceeded = true; | |||||
} | |||||
} | |||||
} | |||||
pos += 1.0; | |||||
} | |||||
pos -= speedRatio; | |||||
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos), indexBuffer); | |||||
} | |||||
} | |||||
subSamplePos = pos; | |||||
if (wrap == 0) | |||||
return (int) (input - originalIn); | |||||
return ((int) (input - originalIn) + wrap) % wrap; | |||||
} | |||||
int interpolateAdding (double speedRatio, | |||||
const float* input, | |||||
float* output, | |||||
int numOutputSamplesToProduce, | |||||
int numInputSamplesAvailable, | |||||
int wrap, | |||||
float gain) noexcept | |||||
{ | |||||
auto originalIn = input; | |||||
auto pos = subSamplePos; | |||||
bool exceeded = false; | |||||
if (speedRatio < 1.0) | |||||
{ | |||||
for (int i = numOutputSamplesToProduce; --i >= 0;) | |||||
{ | |||||
if (pos >= 1.0) | |||||
{ | |||||
if (exceeded) | |||||
{ | |||||
pushInterpolationSample (0.0); | |||||
} | |||||
else | |||||
{ | |||||
pushInterpolationSample (*input++); | |||||
if (--numInputSamplesAvailable <= 0) | |||||
{ | |||||
if (wrap > 0) | |||||
{ | |||||
input -= wrap; | |||||
numInputSamplesAvailable += wrap; | |||||
} | |||||
else | |||||
{ | |||||
numInputSamplesAvailable = true; | |||||
} | |||||
} | |||||
} | |||||
pos -= 1.0; | |||||
} | |||||
*output++ += gain * InterpolatorTraits::valueAtOffset ((float) pos); | |||||
pos += speedRatio; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = numOutputSamplesToProduce; --i >= 0;) | |||||
{ | |||||
while (pos < speedRatio) | |||||
{ | |||||
if (exceeded) | |||||
{ | |||||
pushInterpolationSample (0.0); | |||||
} | |||||
else | |||||
{ | |||||
pushInterpolationSample (*input++); | |||||
if (--numInputSamplesAvailable <= 0) | |||||
{ | |||||
if (wrap > 0) | |||||
{ | |||||
input -= wrap; | |||||
numInputSamplesAvailable += wrap; | |||||
} | |||||
else | |||||
{ | |||||
exceeded = true; | |||||
} | |||||
} | |||||
} | |||||
pos += 1.0; | |||||
} | |||||
pos -= speedRatio; | |||||
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos), indexBuffer); | |||||
} | |||||
} | |||||
subSamplePos = pos; | |||||
if (wrap == 0) | |||||
return (int) (input - originalIn); | |||||
return ((int) (input - originalIn) + wrap) % wrap; | |||||
} | |||||
int interpolateAdding (double speedRatio, | |||||
const float* input, | |||||
float* output, | |||||
int numOutputSamplesToProduce, | |||||
float gain) noexcept | |||||
{ | |||||
auto pos = subSamplePos; | |||||
int numUsed = 0; | |||||
while (numOutputSamplesToProduce > 0) | |||||
{ | |||||
while (pos >= 1.0) | |||||
{ | |||||
pushInterpolationSample (input[numUsed++]); | |||||
pos -= 1.0; | |||||
} | |||||
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer); | |||||
pos += speedRatio; | |||||
--numOutputSamplesToProduce; | |||||
} | |||||
subSamplePos = pos; | |||||
return numUsed; | |||||
} | |||||
//============================================================================== | |||||
float lastInputSamples[(size_t) memorySize]; | |||||
double subSamplePos = 1.0; | |||||
int indexBuffer = 0; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericInterpolator) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,336 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,217 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,191 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 6 End-User License | |||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
End User License Agreement: www.juce.com/juce-6-licence | |||||
Privacy Policy: www.juce.com/juce-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 | |||||
{ | |||||
#if JUCE_UNIT_TESTS | |||||
class InterpolatorTests : public UnitTest | |||||
{ | |||||
public: | |||||
InterpolatorTests() | |||||
: UnitTest ("InterpolatorTests", UnitTestCategories::audio) | |||||
{ | |||||
} | |||||
private: | |||||
template<typename InterpolatorType> | |||||
void runInterplatorTests (const String& interpolatorName) | |||||
{ | |||||
auto createGaussian = [] (std::vector<float>& destination, float scale, float centreInSamples, float width) | |||||
{ | |||||
for (size_t i = 0; i < destination.size(); ++i) | |||||
{ | |||||
auto x = (((float) i) - centreInSamples) * width; | |||||
destination[i] = std::exp (-(x * x)); | |||||
} | |||||
FloatVectorOperations::multiply (destination.data(), scale, (int) destination.size()); | |||||
}; | |||||
auto findGaussianPeak = [] (const std::vector<float>& input) -> float | |||||
{ | |||||
auto max = std::max_element (std::begin (input), std::end (input)); | |||||
auto maxPrev = max - 1; | |||||
jassert (maxPrev >= std::begin (input)); | |||||
auto maxNext = max + 1; | |||||
jassert (maxNext < std::end (input)); | |||||
auto quadraticMaxLoc = (*maxPrev - *maxNext) / (2.0f * ((*maxNext + *maxPrev) - (2.0f * *max))); | |||||
return quadraticMaxLoc + (float) std::distance (std::begin (input), max); | |||||
}; | |||||
auto expectAllElementsWithin = [this] (const std::vector<float>& v1, const std::vector<float>& v2, float tolerance) | |||||
{ | |||||
expectEquals ((int) v1.size(), (int) v2.size()); | |||||
for (size_t i = 0; i < v1.size(); ++i) | |||||
expectWithinAbsoluteError (v1[i], v2[i], tolerance); | |||||
}; | |||||
InterpolatorType interpolator; | |||||
constexpr size_t inputSize = 1001; | |||||
static_assert (inputSize > 800 + InterpolatorType::getBaseLatency(), | |||||
"The test InterpolatorTests input buffer is too small"); | |||||
std::vector<float> input (inputSize); | |||||
constexpr auto inputGaussianMidpoint = (float) (inputSize - 1) / 2.0f; | |||||
constexpr auto inputGaussianValueAtEnds = 0.000001f; | |||||
const auto inputGaussianWidth = std::sqrt (-std::log (inputGaussianValueAtEnds)) / inputGaussianMidpoint; | |||||
createGaussian (input, 1.0f, inputGaussianMidpoint, inputGaussianWidth); | |||||
for (auto speedRatio : { 0.4, 0.8263, 1.0, 1.05, 1.2384, 1.6 }) | |||||
{ | |||||
const auto expectedGaussianMidpoint = (inputGaussianMidpoint + InterpolatorType::getBaseLatency()) / (float) speedRatio; | |||||
const auto expectedGaussianWidth = inputGaussianWidth * (float) speedRatio; | |||||
const auto outputBufferSize = (size_t) std::floor ((float) input.size() / speedRatio); | |||||
for (int numBlocks : { 1, 5 }) | |||||
{ | |||||
const auto inputBlockSize = (float) input.size() / (float) numBlocks; | |||||
const auto outputBlockSize = (int) std::floor (inputBlockSize / speedRatio); | |||||
std::vector<float> output (outputBufferSize, std::numeric_limits<float>::min()); | |||||
beginTest (interpolatorName + " process " + String (numBlocks) + " blocks ratio " + String (speedRatio)); | |||||
interpolator.reset(); | |||||
{ | |||||
auto* inputPtr = input.data(); | |||||
auto* outputPtr = output.data(); | |||||
for (int i = 0; i < numBlocks; ++i) | |||||
{ | |||||
auto numInputSamplesRead = interpolator.process (speedRatio, inputPtr, outputPtr, outputBlockSize); | |||||
inputPtr += numInputSamplesRead; | |||||
outputPtr += outputBlockSize; | |||||
} | |||||
} | |||||
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f); | |||||
std::vector<float> expectedOutput (output.size()); | |||||
createGaussian (expectedOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth); | |||||
expectAllElementsWithin (output, expectedOutput, 0.02f); | |||||
beginTest (interpolatorName + " process adding " + String (numBlocks) + " blocks ratio " + String (speedRatio)); | |||||
interpolator.reset(); | |||||
constexpr float addingGain = 0.7384f; | |||||
{ | |||||
auto* inputPtr = input.data(); | |||||
auto* outputPtr = output.data(); | |||||
for (int i = 0; i < numBlocks; ++i) | |||||
{ | |||||
auto numInputSamplesRead = interpolator.processAdding (speedRatio, inputPtr, outputPtr, outputBlockSize, addingGain); | |||||
inputPtr += numInputSamplesRead; | |||||
outputPtr += outputBlockSize; | |||||
} | |||||
} | |||||
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f); | |||||
std::vector<float> additionalOutput (output.size()); | |||||
createGaussian (additionalOutput, addingGain, expectedGaussianMidpoint, expectedGaussianWidth); | |||||
FloatVectorOperations::add (expectedOutput.data(), additionalOutput.data(), (int) additionalOutput.size()); | |||||
expectAllElementsWithin (output, expectedOutput, 0.02f); | |||||
} | |||||
beginTest (interpolatorName + " process wrap 0 ratio " + String (speedRatio)); | |||||
std::vector<float> doubleLengthOutput (2 * outputBufferSize, std::numeric_limits<float>::min()); | |||||
interpolator.reset(); | |||||
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(), | |||||
(int) input.size(), 0); | |||||
std::vector<float> expectedDoubleLengthOutput (doubleLengthOutput.size()); | |||||
createGaussian (expectedDoubleLengthOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth); | |||||
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f); | |||||
beginTest (interpolatorName + " process wrap double ratio " + String (speedRatio)); | |||||
interpolator.reset(); | |||||
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(), | |||||
(int) input.size(), (int) input.size()); | |||||
std::vector<float> secondGaussian (doubleLengthOutput.size()); | |||||
createGaussian (secondGaussian, 1.0f, expectedGaussianMidpoint + (float) outputBufferSize, expectedGaussianWidth); | |||||
FloatVectorOperations::add (expectedDoubleLengthOutput.data(), secondGaussian.data(), (int) expectedDoubleLengthOutput.size()); | |||||
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f); | |||||
} | |||||
} | |||||
public: | |||||
void runTest() override | |||||
{ | |||||
runInterplatorTests<WindowedSincInterpolator> ("WindowedSincInterpolator"); | |||||
runInterplatorTests<LagrangeInterpolator> ("LagrangeInterpolator"); | |||||
runInterplatorTests<CatmullRomInterpolator> ("CatmullRomInterpolator"); | |||||
runInterplatorTests<LinearInterpolator> ("LinearInterpolator"); | |||||
} | |||||
}; | |||||
static InterpolatorTests interpolatorTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,245 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 6 End-User License | |||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
End User License Agreement: www.juce.com/juce-6-licence | |||||
Privacy Policy: www.juce.com/juce-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 collection of different interpolators for resampling streams of floats. | |||||
@see GenericInterpolator, WindowedSincInterpolator, LagrangeInterpolator, | |||||
CatmullRomInterpolator, LinearInterpolator, ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
class Interpolators | |||||
{ | |||||
private: | |||||
struct WindowedSincTraits | |||||
{ | |||||
static constexpr float algorithmicLatency = 100.0f; | |||||
static forcedinline float windowedSinc (float firstFrac, int index) noexcept | |||||
{ | |||||
auto index2 = index + 1; | |||||
auto frac = firstFrac; | |||||
auto value1 = lookupTable[index]; | |||||
auto value2 = lookupTable[index2]; | |||||
return value1 + (frac * (value2 - value1)); | |||||
} | |||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int indexBuffer) noexcept | |||||
{ | |||||
const int numCrossings = 100; | |||||
const float floatCrossings = (float) numCrossings; | |||||
float result = 0.0f; | |||||
auto samplePosition = indexBuffer; | |||||
float firstFrac = 0.0f; | |||||
float lastSincPosition = -1.0f; | |||||
int index = 0, sign = -1; | |||||
for (int i = -numCrossings; i <= numCrossings; ++i) | |||||
{ | |||||
auto sincPosition = (1.0f - offset) + (float) i; | |||||
if (i == -numCrossings || (sincPosition >= 0 && lastSincPosition < 0)) | |||||
{ | |||||
auto indexFloat = (sincPosition >= 0.f ? sincPosition : -sincPosition) * 100.0f; | |||||
auto indexFloored = std::floor (indexFloat); | |||||
index = (int) indexFloored; | |||||
firstFrac = indexFloat - indexFloored; | |||||
sign = (sincPosition < 0 ? -1 : 1); | |||||
} | |||||
if (sincPosition == 0.0f) | |||||
result += inputs[samplePosition]; | |||||
else if (sincPosition < floatCrossings && sincPosition > -floatCrossings) | |||||
result += inputs[samplePosition] * windowedSinc (firstFrac, index); | |||||
if (++samplePosition == numCrossings * 2) | |||||
samplePosition = 0; | |||||
lastSincPosition = sincPosition; | |||||
index += 100 * sign; | |||||
} | |||||
return result; | |||||
} | |||||
static const float lookupTable[10001]; | |||||
}; | |||||
struct LagrangeTraits | |||||
{ | |||||
static constexpr float algorithmicLatency = 2.0f; | |||||
static float valueAtOffset (const float*, float, int) noexcept; | |||||
}; | |||||
struct CatmullRomTraits | |||||
{ | |||||
//============================================================================== | |||||
static constexpr float algorithmicLatency = 2.0f; | |||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept | |||||
{ | |||||
auto y0 = inputs[index]; if (++index == 4) index = 0; | |||||
auto y1 = inputs[index]; if (++index == 4) index = 0; | |||||
auto y2 = inputs[index]; if (++index == 4) index = 0; | |||||
auto y3 = inputs[index]; | |||||
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)))))); | |||||
} | |||||
}; | |||||
struct LinearTraits | |||||
{ | |||||
static constexpr float algorithmicLatency = 1.0f; | |||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept | |||||
{ | |||||
auto y0 = inputs[index]; | |||||
auto y1 = inputs[index == 0 ? 1 : 0]; | |||||
return y1 * offset + y0 * (1.0f - offset); | |||||
} | |||||
}; | |||||
struct ZeroOrderHoldTraits | |||||
{ | |||||
static constexpr float algorithmicLatency = 0.0f; | |||||
static forcedinline float valueAtOffset (const float* const inputs, const float, int) noexcept | |||||
{ | |||||
return inputs[0]; | |||||
} | |||||
}; | |||||
public: | |||||
using WindowedSinc = GenericInterpolator<WindowedSincTraits, 200>; | |||||
using Lagrange = GenericInterpolator<LagrangeTraits, 5>; | |||||
using CatmullRom = GenericInterpolator<CatmullRomTraits, 4>; | |||||
using Linear = GenericInterpolator<LinearTraits, 2>; | |||||
using ZeroOrderHold = GenericInterpolator<ZeroOrderHoldTraits, 1>; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
An interpolator for resampling a stream of floats using high order windowed | |||||
(hann) sinc interpolation, recommended for high quality resampling. | |||||
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 LinearInterpolator object. | |||||
@see GenericInterpolator | |||||
@see LagrangeInterpolator, CatmullRomInterpolator, LinearInterpolator, | |||||
ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
using WindowedSincInterpolator = Interpolators::WindowedSinc; | |||||
/** | |||||
An 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 GenericInterpolator | |||||
@see CatmullRomInterpolator, WindowedSincInterpolator, LinearInterpolator, | |||||
ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
using LagrangeInterpolator = Interpolators::Lagrange; | |||||
/** | |||||
An 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 GenericInterpolator | |||||
@see LagrangeInterpolator, WindowedSincInterpolator, LinearInterpolator, | |||||
ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
using CatmullRomInterpolator = Interpolators::CatmullRom; | |||||
/** | |||||
An interpolator for resampling a stream of floats using linear 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 LinearInterpolator object. | |||||
@see GenericInterpolator | |||||
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator, | |||||
ZeroOrderHoldInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
using LinearInterpolator = Interpolators::Linear; | |||||
/** | |||||
An interpolator for resampling a stream of floats using zero order hold | |||||
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 ZeroOrderHoldInterpolator | |||||
object. | |||||
@see GenericInterpolator | |||||
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator, | |||||
LinearInterpolator | |||||
@tags{Audio} | |||||
*/ | |||||
using ZeroOrderHoldInterpolator = Interpolators::ZeroOrderHold; | |||||
} // namespace juce |
@@ -0,0 +1,62 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
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 {} | |||||
}; | |||||
template <int k> | |||||
static 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; | |||||
} | |||||
float Interpolators::LagrangeTraits::valueAtOffset (const float* inputs, float offset, int index) noexcept | |||||
{ | |||||
float result = 0.0f; | |||||
result += calcCoefficient<0> (inputs[index], offset); if (++index == 5) index = 0; | |||||
result += calcCoefficient<1> (inputs[index], offset); if (++index == 5) index = 0; | |||||
result += calcCoefficient<2> (inputs[index], offset); if (++index == 5) index = 0; | |||||
result += calcCoefficient<3> (inputs[index], offset); if (++index == 5) index = 0; | |||||
result += calcCoefficient<4> (inputs[index], offset); | |||||
return result; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,313 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ | |||||
float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ | |||||
float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */ | |||||
float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */ | |||||
float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ | |||||
float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 | |||||
put the reverb into a continuous feedback loop. */ | |||||
}; | |||||
//============================================================================== | |||||
/** 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.setTargetValue (newParams.dryLevel * dryScaleFactor); | |||||
wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width)); | |||||
wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width)); | |||||
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; | |||||
parameters = newParams; | |||||
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.setTargetValue (dampingToUse); | |||||
feedback.setTargetValue (roomSizeToUse); | |||||
} | |||||
//============================================================================== | |||||
class CombFilter | |||||
{ | |||||
public: | |||||
CombFilter() noexcept {} | |||||
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 = 0, bufferIndex = 0; | |||||
float last = 0.0f; | |||||
JUCE_DECLARE_NON_COPYABLE (CombFilter) | |||||
}; | |||||
//============================================================================== | |||||
class AllPassFilter | |||||
{ | |||||
public: | |||||
AllPassFilter() noexcept {} | |||||
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 = 0, bufferIndex = 0; | |||||
JUCE_DECLARE_NON_COPYABLE (AllPassFilter) | |||||
}; | |||||
//============================================================================== | |||||
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; | |||||
Parameters parameters; | |||||
float gain; | |||||
CombFilter comb [numChannels][numCombs]; | |||||
AllPassFilter allPass [numChannels][numAllPasses]; | |||||
SmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_UNIT_TESTS | |||||
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Linear>> commonLinearSmoothedValueTests; | |||||
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Multiplicative>> commonMultiplicativeSmoothedValueTests; | |||||
class SmoothedValueTests : public UnitTest | |||||
{ | |||||
public: | |||||
SmoothedValueTests() | |||||
: UnitTest ("SmoothedValueTests", UnitTestCategories::smoothedValues) | |||||
{} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("Linear moving target"); | |||||
{ | |||||
SmoothedValue<float, ValueSmoothingTypes::Linear> sv; | |||||
sv.reset (12); | |||||
float initialValue = 0.0f; | |||||
sv.setCurrentAndTargetValue (initialValue); | |||||
sv.setTargetValue (1.0f); | |||||
auto delta = sv.getNextValue() - initialValue; | |||||
sv.skip (6); | |||||
auto newInitialValue = sv.getCurrentValue(); | |||||
sv.setTargetValue (newInitialValue + 2.0f); | |||||
auto doubleDelta = sv.getNextValue() - newInitialValue; | |||||
expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); | |||||
} | |||||
beginTest ("Multiplicative curve"); | |||||
{ | |||||
SmoothedValue<double, ValueSmoothingTypes::Multiplicative> sv; | |||||
auto numSamples = 12; | |||||
AudioBuffer<double> values (2, numSamples + 1); | |||||
sv.reset (numSamples); | |||||
sv.setCurrentAndTargetValue (1.0); | |||||
sv.setTargetValue (2.0f); | |||||
values.setSample (0, 0, sv.getCurrentValue()); | |||||
for (int i = 1; i < values.getNumSamples(); ++i) | |||||
values.setSample (0, i, sv.getNextValue()); | |||||
sv.setTargetValue (1.0f); | |||||
values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue()); | |||||
for (int i = values.getNumSamples() - 2; i >= 0 ; --i) | |||||
values.setSample (1, i, sv.getNextValue()); | |||||
for (int i = 0; i < values.getNumSamples(); ++i) | |||||
expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9); | |||||
} | |||||
} | |||||
}; | |||||
static SmoothedValueTests smoothedValueTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,630 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A base class for the smoothed value classes. | |||||
This class is used to provide common functionality to the SmoothedValue and | |||||
dsp::LogRampedValue classes. | |||||
@tags{Audio} | |||||
*/ | |||||
template <typename SmoothedValueType> | |||||
class SmoothedValueBase | |||||
{ | |||||
private: | |||||
//============================================================================== | |||||
template <typename T> struct FloatTypeHelper; | |||||
template <template <typename> class SmoothedValueClass, typename FloatType> | |||||
struct FloatTypeHelper <SmoothedValueClass <FloatType>> | |||||
{ | |||||
using Type = FloatType; | |||||
}; | |||||
template <template <typename, typename> class SmoothedValueClass, typename FloatType, typename SmoothingType> | |||||
struct FloatTypeHelper <SmoothedValueClass <FloatType, SmoothingType>> | |||||
{ | |||||
using Type = FloatType; | |||||
}; | |||||
public: | |||||
using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type; | |||||
//============================================================================== | |||||
/** Constructor. */ | |||||
SmoothedValueBase() = default; | |||||
virtual ~SmoothedValueBase() {} | |||||
//============================================================================== | |||||
/** Returns true if the current value is currently being interpolated. */ | |||||
bool isSmoothing() const noexcept { return countdown > 0; } | |||||
/** Returns the current value of the ramp. */ | |||||
FloatType getCurrentValue() const noexcept { return currentValue; } | |||||
//============================================================================== | |||||
/** Returns the target value towards which the smoothed value is currently moving. */ | |||||
FloatType getTargetValue() const noexcept { return target; } | |||||
/** Sets the current value and the target value. | |||||
@param newValue the new value to take | |||||
*/ | |||||
void setCurrentAndTargetValue (FloatType newValue) | |||||
{ | |||||
target = currentValue = newValue; | |||||
countdown = 0; | |||||
} | |||||
//============================================================================== | |||||
/** Applies a 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] *= getNextSmoothedValue(); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::multiply (samples, target, numSamples); | |||||
} | |||||
} | |||||
/** Computes output as a 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] * getNextSmoothedValue(); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples); | |||||
} | |||||
} | |||||
/** Applies a smoothed gain to a buffer */ | |||||
void applyGain (AudioBuffer<FloatType>& buffer, int numSamples) noexcept | |||||
{ | |||||
jassert (numSamples >= 0); | |||||
if (isSmoothing()) | |||||
{ | |||||
if (buffer.getNumChannels() == 1) | |||||
{ | |||||
auto* samples = buffer.getWritePointer (0); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
samples[i] *= getNextSmoothedValue(); | |||||
} | |||||
else | |||||
{ | |||||
for (auto i = 0; i < numSamples; ++i) | |||||
{ | |||||
auto gain = getNextSmoothedValue(); | |||||
for (int channel = 0; channel < buffer.getNumChannels(); channel++) | |||||
buffer.setSample (channel, i, buffer.getSample (channel, i) * gain); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
buffer.applyGain (0, numSamples, target); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
FloatType getNextSmoothedValue() noexcept | |||||
{ | |||||
return static_cast <SmoothedValueType*> (this)->getNextValue(); | |||||
} | |||||
protected: | |||||
//============================================================================== | |||||
FloatType currentValue = 0; | |||||
FloatType target = currentValue; | |||||
int countdown = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A namespace containing a set of types used for specifying the smoothing | |||||
behaviour of the SmoothedValue class. | |||||
For example: | |||||
@code | |||||
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> frequency (1.0f); | |||||
@endcode | |||||
*/ | |||||
namespace ValueSmoothingTypes | |||||
{ | |||||
/** | |||||
Used to indicate a linear smoothing between values. | |||||
@tags{Audio} | |||||
*/ | |||||
struct Linear {}; | |||||
/** | |||||
Used to indicate a smoothing between multiplicative values. | |||||
@tags{Audio} | |||||
*/ | |||||
struct Multiplicative {}; | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
A utility class for values that need smoothing to avoid audio glitches. | |||||
A ValueSmoothingTypes::Linear template parameter selects linear smoothing, | |||||
which increments the SmoothedValue linearly towards its target value. | |||||
@code | |||||
SmoothedValue<float, ValueSmoothingTypes::Linear> yourSmoothedValue; | |||||
@endcode | |||||
A ValueSmoothingTypes::Multiplicative template parameter selects | |||||
multiplicative smoothing increments towards the target value. | |||||
@code | |||||
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue; | |||||
@endcode | |||||
Multiplicative smoothing is useful when you are dealing with | |||||
exponential/logarithmic values like volume in dB or frequency in Hz. For | |||||
example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the | |||||
frequency with an equal temperament tuning across the octave. A 10 step | |||||
smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in | |||||
increments of 1 dB. | |||||
Note that when you are using multiplicative smoothing you cannot ever reach a | |||||
target value of zero! | |||||
@tags{Audio} | |||||
*/ | |||||
template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear> | |||||
class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>> | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Constructor. */ | |||||
SmoothedValue() noexcept | |||||
: SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1)) | |||||
{ | |||||
} | |||||
/** Constructor. */ | |||||
SmoothedValue (FloatType initialValue) noexcept | |||||
{ | |||||
// Multiplicative smoothed values cannot ever reach 0! | |||||
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && initialValue == 0)); | |||||
// Visual Studio can't handle base class initialisation with CRTP | |||||
this->currentValue = initialValue; | |||||
this->target = this->currentValue; | |||||
} | |||||
//============================================================================== | |||||
/** Reset to a new sample rate and ramp length. | |||||
@param sampleRate The sample rate | |||||
@param rampLengthInSeconds The duration of the ramp in seconds | |||||
*/ | |||||
void reset (double sampleRate, double rampLengthInSeconds) noexcept | |||||
{ | |||||
jassert (sampleRate > 0 && rampLengthInSeconds >= 0); | |||||
reset ((int) std::floor (rampLengthInSeconds * sampleRate)); | |||||
} | |||||
/** Set a new ramp length directly in samples. | |||||
@param numSteps The number of samples over which the ramp should be active | |||||
*/ | |||||
void reset (int numSteps) noexcept | |||||
{ | |||||
stepsToTarget = numSteps; | |||||
this->setCurrentAndTargetValue (this->target); | |||||
} | |||||
//============================================================================== | |||||
/** Set the next value to ramp towards. | |||||
@param newValue The new target value | |||||
*/ | |||||
void setTargetValue (FloatType newValue) noexcept | |||||
{ | |||||
if (newValue == this->target) | |||||
return; | |||||
if (stepsToTarget <= 0) | |||||
{ | |||||
this->setCurrentAndTargetValue (newValue); | |||||
return; | |||||
} | |||||
// Multiplicative smoothed values cannot ever reach 0! | |||||
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0)); | |||||
this->target = newValue; | |||||
this->countdown = stepsToTarget; | |||||
setStepSize(); | |||||
} | |||||
//============================================================================== | |||||
/** Compute the next value. | |||||
@returns Smoothed value | |||||
*/ | |||||
FloatType getNextValue() noexcept | |||||
{ | |||||
if (! this->isSmoothing()) | |||||
return this->target; | |||||
--(this->countdown); | |||||
if (this->isSmoothing()) | |||||
setNextValue(); | |||||
else | |||||
this->currentValue = this->target; | |||||
return this->currentValue; | |||||
} | |||||
//============================================================================== | |||||
/** Skip the next numSamples samples. | |||||
This is identical to calling getNextValue numSamples times. It returns | |||||
the new current value. | |||||
@see getNextValue | |||||
*/ | |||||
FloatType skip (int numSamples) noexcept | |||||
{ | |||||
if (numSamples >= this->countdown) | |||||
{ | |||||
this->setCurrentAndTargetValue (this->target); | |||||
return this->target; | |||||
} | |||||
skipCurrentValue (numSamples); | |||||
this->countdown -= numSamples; | |||||
return this->currentValue; | |||||
} | |||||
//============================================================================== | |||||
/** THIS FUNCTION IS DEPRECATED. | |||||
Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead: | |||||
lsv.setValue (x, false); -> lsv.setTargetValue (x); | |||||
lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x); | |||||
@param newValue The new target value | |||||
@param force If true, the value will be set immediately, bypassing the ramp | |||||
*/ | |||||
JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept, | |||||
{ | |||||
if (force) | |||||
{ | |||||
this->setCurrentAndTargetValue (newValue); | |||||
return; | |||||
} | |||||
setTargetValue (newValue); | |||||
}) | |||||
private: | |||||
//============================================================================== | |||||
template <typename T> | |||||
using LinearVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Linear>::value, void>::type; | |||||
template <typename T> | |||||
using MultiplicativeVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Multiplicative>::value, void>::type; | |||||
//============================================================================== | |||||
template <typename T = SmoothingType> | |||||
LinearVoid<T> setStepSize() noexcept | |||||
{ | |||||
step = (this->target - this->currentValue) / (FloatType) this->countdown; | |||||
} | |||||
template <typename T = SmoothingType> | |||||
MultiplicativeVoid<T> setStepSize() | |||||
{ | |||||
step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / (FloatType) this->countdown); | |||||
} | |||||
//============================================================================== | |||||
template <typename T = SmoothingType> | |||||
LinearVoid<T> setNextValue() noexcept | |||||
{ | |||||
this->currentValue += step; | |||||
} | |||||
template <typename T = SmoothingType> | |||||
MultiplicativeVoid<T> setNextValue() noexcept | |||||
{ | |||||
this->currentValue *= step; | |||||
} | |||||
//============================================================================== | |||||
template <typename T = SmoothingType> | |||||
LinearVoid<T> skipCurrentValue (int numSamples) noexcept | |||||
{ | |||||
this->currentValue += step * (FloatType) numSamples; | |||||
} | |||||
template <typename T = SmoothingType> | |||||
MultiplicativeVoid<T> skipCurrentValue (int numSamples) | |||||
{ | |||||
this->currentValue *= (FloatType) std::pow (step, numSamples); | |||||
} | |||||
//============================================================================== | |||||
FloatType step = FloatType(); | |||||
int stepsToTarget = 0; | |||||
}; | |||||
template <typename FloatType> | |||||
using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>; | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
template <class SmoothedValueType> | |||||
class CommonSmoothedValueTests : public UnitTest | |||||
{ | |||||
public: | |||||
CommonSmoothedValueTests() | |||||
: UnitTest ("CommonSmoothedValueTests", UnitTestCategories::smoothedValues) | |||||
{} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("Initial state"); | |||||
{ | |||||
SmoothedValueType sv; | |||||
auto value = sv.getCurrentValue(); | |||||
expectEquals (sv.getTargetValue(), value); | |||||
sv.getNextValue(); | |||||
expectEquals (sv.getCurrentValue(), value); | |||||
expect (! sv.isSmoothing()); | |||||
} | |||||
beginTest ("Resetting"); | |||||
{ | |||||
auto initialValue = 15.0f; | |||||
SmoothedValueType sv (initialValue); | |||||
sv.reset (3); | |||||
expectEquals (sv.getCurrentValue(), initialValue); | |||||
auto targetValue = initialValue + 1.0f; | |||||
sv.setTargetValue (targetValue); | |||||
expectEquals (sv.getTargetValue(), targetValue); | |||||
expectEquals (sv.getCurrentValue(), initialValue); | |||||
expect (sv.isSmoothing()); | |||||
auto currentValue = sv.getNextValue(); | |||||
expect (currentValue > initialValue); | |||||
expectEquals (sv.getCurrentValue(), currentValue); | |||||
expectEquals (sv.getTargetValue(), targetValue); | |||||
expect (sv.isSmoothing()); | |||||
sv.reset (5); | |||||
expectEquals (sv.getCurrentValue(), targetValue); | |||||
expectEquals (sv.getTargetValue(), targetValue); | |||||
expect (! sv.isSmoothing()); | |||||
sv.getNextValue(); | |||||
expectEquals (sv.getCurrentValue(), targetValue); | |||||
sv.setTargetValue (1.5f); | |||||
sv.getNextValue(); | |||||
float newStart = 0.2f; | |||||
sv.setCurrentAndTargetValue (newStart); | |||||
expectEquals (sv.getNextValue(), newStart); | |||||
expectEquals (sv.getTargetValue(), newStart); | |||||
expectEquals (sv.getCurrentValue(), newStart); | |||||
expect (! sv.isSmoothing()); | |||||
} | |||||
beginTest ("Sample rate"); | |||||
{ | |||||
SmoothedValueType svSamples { 3.0f }; | |||||
auto svTime = svSamples; | |||||
auto numSamples = 12; | |||||
svSamples.reset (numSamples); | |||||
svTime.reset (numSamples * 2, 1.0); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
svTime.skip (1); | |||||
expectWithinAbsoluteError (svSamples.getNextValue(), | |||||
svTime.getNextValue(), | |||||
1.0e-7f); | |||||
} | |||||
} | |||||
beginTest ("Block processing"); | |||||
{ | |||||
SmoothedValueType sv (1.0f); | |||||
sv.reset (12); | |||||
sv.setTargetValue (2.0f); | |||||
const auto numSamples = 15; | |||||
AudioBuffer<float> referenceData (1, numSamples); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
referenceData.setSample (0, i, sv.getNextValue()); | |||||
expect (referenceData.getSample (0, 0) > 0); | |||||
expect (referenceData.getSample (0, 10) < sv.getTargetValue()); | |||||
expectWithinAbsoluteError (referenceData.getSample (0, 11), | |||||
sv.getTargetValue(), | |||||
1.0e-7f); | |||||
auto getUnitData = [] (int numSamplesToGenerate) | |||||
{ | |||||
AudioBuffer<float> result (1, numSamplesToGenerate); | |||||
for (int i = 0; i < numSamplesToGenerate; ++i) | |||||
result.setSample (0, i, 1.0f); | |||||
return result; | |||||
}; | |||||
auto compareData = [this] (const AudioBuffer<float>& test, | |||||
const AudioBuffer<float>& reference) | |||||
{ | |||||
for (int i = 0; i < test.getNumSamples(); ++i) | |||||
expectWithinAbsoluteError (test.getSample (0, i), | |||||
reference.getSample (0, i), | |||||
1.0e-7f); | |||||
}; | |||||
auto testData = getUnitData (numSamples); | |||||
sv.setCurrentAndTargetValue (1.0f); | |||||
sv.setTargetValue (2.0f); | |||||
sv.applyGain (testData.getWritePointer (0), numSamples); | |||||
compareData (testData, referenceData); | |||||
testData = getUnitData (numSamples); | |||||
AudioBuffer<float> destData (1, numSamples); | |||||
sv.setCurrentAndTargetValue (1.0f); | |||||
sv.setTargetValue (2.0f); | |||||
sv.applyGain (destData.getWritePointer (0), | |||||
testData.getReadPointer (0), | |||||
numSamples); | |||||
compareData (destData, referenceData); | |||||
compareData (testData, getUnitData (numSamples)); | |||||
testData = getUnitData (numSamples); | |||||
sv.setCurrentAndTargetValue (1.0f); | |||||
sv.setTargetValue (2.0f); | |||||
sv.applyGain (testData, numSamples); | |||||
compareData (testData, referenceData); | |||||
} | |||||
beginTest ("Skip"); | |||||
{ | |||||
SmoothedValueType sv; | |||||
sv.reset (12); | |||||
sv.setCurrentAndTargetValue (1.0f); | |||||
sv.setTargetValue (2.0f); | |||||
Array<float> reference; | |||||
for (int i = 0; i < 15; ++i) | |||||
reference.add (sv.getNextValue()); | |||||
sv.setCurrentAndTargetValue (1.0f); | |||||
sv.setTargetValue (2.0f); | |||||
expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f); | |||||
expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f); | |||||
expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f); | |||||
sv.skip (3); | |||||
expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f); | |||||
expectEquals (sv.skip (300), sv.getTargetValue()); | |||||
expectEquals (sv.getCurrentValue(), sv.getTargetValue()); | |||||
} | |||||
beginTest ("Negative"); | |||||
{ | |||||
SmoothedValueType sv; | |||||
auto numValues = 12; | |||||
sv.reset (numValues); | |||||
std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f }, | |||||
{ -100.0f, -3.0f } }; | |||||
for (auto range : ranges) | |||||
{ | |||||
auto start = range.first, end = range.second; | |||||
sv.setCurrentAndTargetValue (start); | |||||
sv.setTargetValue (end); | |||||
auto val = sv.skip (numValues / 2); | |||||
if (end > start) | |||||
expect (val > start && val < end); | |||||
else | |||||
expect (val < start && val > end); | |||||
auto nextVal = sv.getNextValue(); | |||||
expect (end > start ? (nextVal > val) : (nextVal < val)); | |||||
auto endVal = sv.skip (500); | |||||
expectEquals (endVal, end); | |||||
expectEquals (sv.getNextValue(), end); | |||||
expectEquals (sv.getCurrentValue(), end); | |||||
sv.setCurrentAndTargetValue (start); | |||||
sv.setTargetValue (end); | |||||
SmoothedValueType positiveSv { -start }; | |||||
positiveSv.reset (numValues); | |||||
positiveSv.setTargetValue (-end); | |||||
for (int i = 0; i < numValues + 2; ++i) | |||||
expectEquals (sv.getNextValue(), -positiveSv.getNextValue()); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,552 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() override; | |||||
//============================================================================== | |||||
/** | |||||
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 | |||||
{ | |||||
/** 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 = 0; | |||||
/** 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 = 0; | |||||
/** 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 = true; | |||||
/** 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 = true; | |||||
bool operator== (const AudioDeviceSetup&) const; | |||||
bool operator!= (const AudioDeviceSetup&) const; | |||||
}; | |||||
//============================================================================== | |||||
/** 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). | |||||
*/ | |||||
std::unique_ptr<XmlElement> createStateXml() const; | |||||
//============================================================================== | |||||
/** Returns the current device properties that are in use. | |||||
@see setAudioDeviceSetup | |||||
*/ | |||||
AudioDeviceSetup getAudioDeviceSetup() const; | |||||
/** Returns the current device properties that are in use. | |||||
This is an old method, kept around for compatibility, but you should prefer the new | |||||
version which returns the result rather than taking an out-parameter. | |||||
@see getAudioDeviceSetup() | |||||
*/ | |||||
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::getAvailableDevices() method. | |||||
Any incoming messages from enabled input devices will be forwarded on to all the | |||||
listeners that have been registered with the addMidiInputDeviceCallback() 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 addMidiInputDeviceCallback, isMidiInputDeviceEnabled | |||||
*/ | |||||
void setMidiInputDeviceEnabled (const String& deviceIdentifier, bool enabled); | |||||
/** Returns true if a given midi input device is being used. | |||||
@see setMidiInputDeviceEnabled | |||||
*/ | |||||
bool isMidiInputDeviceEnabled (const String& deviceIdentifier) const; | |||||
/** Registers a listener for callbacks when midi events arrive from a midi input. | |||||
The device identifier can be empty to indicate that it wants to receive all incoming | |||||
events from all the enabled MIDI inputs. Or it can be the identifier of one of the | |||||
MIDI input devices if it just wants the events from that device. (see | |||||
MidiInput::getAvailableDevices() for the list of devices). | |||||
Only devices which are enabled (see the setMidiInputDeviceEnabled() method) will have their | |||||
events forwarded on to listeners. | |||||
*/ | |||||
void addMidiInputDeviceCallback (const String& deviceIdentifier, | |||||
MidiInputCallback* callback); | |||||
/** Removes a listener that was previously registered with addMidiInputDeviceCallback(). */ | |||||
void removeMidiInputDeviceCallback (const String& deviceIdentifier, | |||||
MidiInputCallback* callback); | |||||
//============================================================================== | |||||
/** Sets a midi output device to use as the default. | |||||
The list of devices can be obtained with the MidiOutput::getAvailableDevices() 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::getDefaultDevice(). | |||||
@see getDefaultMidiOutput, getDefaultMidiOutputIdentifier | |||||
*/ | |||||
void setDefaultMidiOutputDevice (const String& deviceIdentifier); | |||||
/** Returns the name of the default midi output. | |||||
@see setDefaultMidiOutputDevice, getDefaultMidiOutput | |||||
*/ | |||||
const String& getDefaultMidiOutputIdentifier() const noexcept { return defaultMidiOutputDeviceInfo.identifier; } | |||||
/** 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 getDefaultMidiOutputIdentifier | |||||
*/ | |||||
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. */ | |||||
void addAudioDeviceType (std::unique_ptr<AudioIODeviceType> newDeviceType); | |||||
/** Removes a previously added device type from the manager. */ | |||||
void removeAudioDeviceType (AudioIODeviceType* deviceTypeToRemove); | |||||
//============================================================================== | |||||
/** 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 output level. | |||||
You need to store this object locally to ensure that the reference count is incremented | |||||
and decremented properly. The current output 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; | |||||
//============================================================================== | |||||
/** Deprecated. */ | |||||
void setMidiInputEnabled (const String&, bool); | |||||
/** Deprecated. */ | |||||
bool isMidiInputEnabled (const String&) const; | |||||
/** Deprecated. */ | |||||
void addMidiInputCallback (const String&, MidiInputCallback*); | |||||
/** Deprecated. */ | |||||
void removeMidiInputCallback (const String&, MidiInputCallback*); | |||||
/** Deprecated. */ | |||||
void setDefaultMidiOutput (const String&); | |||||
/** Deprecated. */ | |||||
const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputDeviceInfo.name; } | |||||
private: | |||||
//============================================================================== | |||||
OwnedArray<AudioIODeviceType> availableDeviceTypes; | |||||
OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs; | |||||
AudioDeviceSetup currentSetup; | |||||
std::unique_ptr<AudioIODevice> currentAudioDevice; | |||||
Array<AudioIODeviceCallback*> callbacks; | |||||
int numInputChansNeeded = 0, numOutputChansNeeded = 2; | |||||
String preferredDeviceName, currentDeviceType; | |||||
std::unique_ptr<XmlElement> lastExplicitSettings; | |||||
mutable bool listNeedsScanning = true; | |||||
AudioBuffer<float> tempBuffer; | |||||
struct MidiCallbackInfo | |||||
{ | |||||
String deviceIdentifier; | |||||
MidiInputCallback* callback; | |||||
}; | |||||
Array<MidiDeviceInfo> midiDeviceInfosFromXml; | |||||
std::vector<std::unique_ptr<MidiInput>> enabledMidiInputs; | |||||
Array<MidiCallbackInfo> midiCallbacks; | |||||
MidiDeviceInfo defaultMidiOutputDeviceInfo; | |||||
std::unique_ptr<MidiOutput> defaultMidiOutput; | |||||
CriticalSection audioCallbackLock, midiCallbackLock; | |||||
std::unique_ptr<AudioBuffer<float>> testSound; | |||||
int testSoundPosition = 0; | |||||
AudioProcessLoadMeasurer loadMeasurer; | |||||
LevelMeter::Ptr inputLevelGetter { new LevelMeter() }, | |||||
outputLevelGetter { new LevelMeter() }; | |||||
//============================================================================== | |||||
class 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 |
@@ -0,0 +1,45 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,325 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
/** 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) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,184 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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() = default; | |||||
/** 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 |
@@ -0,0 +1,59 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 |
@@ -0,0 +1,261 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 && JUCE_MSVC | |||||
/* 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 you may | |||||
need to 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> | |||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265) | |||||
#include <wrl/event.h> | |||||
JUCE_END_IGNORE_WARNINGS_MSVC | |||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4467) | |||||
#include <robuffer.h> | |||||
JUCE_END_IGNORE_WARNINGS_MSVC | |||||
#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> | |||||
#include <Midi.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 | |||||
#if JUCE_USE_ANDROID_OPENSLES | |||||
#error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES" | |||||
#endif | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", | |||||
"-Wzero-as-null-pointer-constant", | |||||
"-Winconsistent-missing-destructor-override", | |||||
"-Wshadow-field-in-constructor", | |||||
"-Wshadow-field") | |||||
#include <oboe/Oboe.h> | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#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_MidiDevices.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 | |||||
#if JUCE_JACK | |||||
#include "native/juce_linux_JackAudio.cpp" | |||||
#endif | |||||
#if JUCE_BELA | |||||
#include "native/juce_linux_Bela.cpp" | |||||
#else | |||||
#include "native/juce_linux_Midi.cpp" | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_ANDROID | |||||
#include "native/juce_android_Audio.cpp" | |||||
#include "native/juce_android_Midi.cpp" | |||||
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE | |||||
#include "native/juce_android_HighPerformanceAudioHelpers.h" | |||||
#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 | |||||
#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 |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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.md file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_devices | |||||
vendor: juce | |||||
version: 6.0.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: 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_USE_WINRT_MIDI | |||||
Enables the use of the Windows Runtime API for MIDI, allowing connections | |||||
to Bluetooth Low Energy devices on Windows 10 version 1809 (October 2018 | |||||
Update) and later. If you enable this flag then older versions of Windows | |||||
will automatically fall back to using the regular Win32 MIDI API. | |||||
You will need version 10.0.14393.0 of the Windows Standalone SDK to compile | |||||
and you may need to add the path to the WinRT headers. The path to the | |||||
headers will 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_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 | |||||
Enables Oboe devices (Android only, API 16 or above). | |||||
*/ | |||||
#ifndef JUCE_USE_ANDROID_OBOE | |||||
#define JUCE_USE_ANDROID_OBOE 1 | |||||
#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_OBOE_STABILIZED_CALLBACK | |||||
If JUCE_USE_ANDROID_OBOE is enabled, enabling this will wrap output audio | |||||
streams in the oboe::StabilizedCallback class. This class attempts to keep | |||||
the CPU spinning to avoid it being scaled down on certain devices. | |||||
*/ | |||||
#ifndef JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK | |||||
#define JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK 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_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_MidiDevices.h" | |||||
#include "midi_io/juce_MidiMessageCollector.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 |
@@ -0,0 +1,23 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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" |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier) | |||||
: Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier) | |||||
{ | |||||
} | |||||
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer) | |||||
{ | |||||
for (const auto metadata : buffer) | |||||
sendMessageNow (metadata.getMessage()); | |||||
} | |||||
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; | |||||
for (const auto metadata : buffer) | |||||
{ | |||||
auto eventTime = millisecondCounterToStartAt + timeScaleFactor * metadata.samplePosition; | |||||
auto* m = new PendingMessage (metadata.data, metadata.numBytes, 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()) | |||||
{ | |||||
auto 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 |
@@ -0,0 +1,378 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 contains information about a MIDI input or output device. | |||||
You can get one of these structs by calling the static getAvailableDevices() or | |||||
getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo() | |||||
on an instance of these classes. Devices can be opened by passing the identifier to | |||||
the openDevice() method. | |||||
@tags{Audio} | |||||
*/ | |||||
struct MidiDeviceInfo | |||||
{ | |||||
MidiDeviceInfo() = default; | |||||
MidiDeviceInfo (const String& deviceName, const String& deviceIdentifier) | |||||
: name (deviceName), identifier (deviceIdentifier) | |||||
{ | |||||
} | |||||
/** The name of this device. | |||||
This will be provided by the OS unless the device has been created with the | |||||
createNewDevice() method. | |||||
Note that the name is not guaranteed to be unique and two devices with the | |||||
same name will be indistinguishable. If you want to address a specific device | |||||
it is better to use the identifier. | |||||
*/ | |||||
String name; | |||||
/** The identifier for this device. | |||||
This will be provided by the OS and it's format will differ on different systems | |||||
e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string. | |||||
*/ | |||||
String identifier; | |||||
//============================================================================== | |||||
bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; } | |||||
bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); } | |||||
}; | |||||
class MidiInputCallback; | |||||
//============================================================================== | |||||
/** | |||||
Represents a midi input device. | |||||
To create one of these, use the static getAvailableDevices() 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 final | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi input devices. | |||||
You can open one of the devices by passing its identifier into the openDevice() method. | |||||
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static Array<MidiDeviceInfo> getAvailableDevices(); | |||||
/** Returns the MidiDeviceInfo of the default midi input device to use. */ | |||||
static MidiDeviceInfo getDefaultDevice(); | |||||
/** 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. | |||||
If the device can't be opened, this will return an empty object. | |||||
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||||
find the available devices that can be opened | |||||
@param callback the object that will receive the midi messages from this device | |||||
@see MidiInputCallback, getDevices | |||||
*/ | |||||
static std::unique_ptr<MidiInput> openDevice (const String& deviceIdentifier, MidiInputCallback* callback); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi input device (only available on Linux, macOS and iOS). | |||||
This will attempt to create a new midi input device with the specified name for other | |||||
apps to connect to. | |||||
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability" | |||||
setting in the iOS exporter otherwise this method will fail. | |||||
Returns an empty object if a device can't be created. | |||||
@param deviceName the name of the device to create | |||||
@param callback the object that will receive the midi messages from this device | |||||
*/ | |||||
static std::unique_ptr<MidiInput> createNewDevice (const String& deviceName, MidiInputCallback* callback); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiInput(); | |||||
/** 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(); | |||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||||
/** Returns the identifier of this device. */ | |||||
String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||||
/** Returns the name of this device. */ | |||||
String getName() const noexcept { return deviceInfo.name; } | |||||
/** Sets a custom name for the device. */ | |||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||||
//============================================================================== | |||||
/** Deprecated. */ | |||||
static StringArray getDevices(); | |||||
/** Deprecated. */ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Deprecated. */ | |||||
static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*); | |||||
private: | |||||
//============================================================================== | |||||
explicit MidiInput (const String&, const String&); | |||||
MidiDeviceInfo deviceInfo; | |||||
void* internal = nullptr; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (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() = default; | |||||
/** 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 output device. | |||||
To create one of these, use the static getAvailableDevices() method to find out what | |||||
outputs are available, and then use the openDevice() method to try to open one. | |||||
@see MidiInput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiOutput final : private Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi output devices. | |||||
You can open one of the devices by passing its identifier into the openDevice() method. | |||||
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static Array<MidiDeviceInfo> getAvailableDevices(); | |||||
/** Returns the MidiDeviceInfo of the default midi output device to use. */ | |||||
static MidiDeviceInfo getDefaultDevice(); | |||||
/** Tries to open one of the midi output devices. | |||||
This will return a MidiOutput object if it manages to open it, you can then | |||||
send messages to this device. | |||||
If the device can't be opened, this will return an empty object. | |||||
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||||
find the available devices that can be opened | |||||
@see getDevices | |||||
*/ | |||||
static std::unique_ptr<MidiOutput> openDevice (const String& deviceIdentifier); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi output device (only available on Linux, macOS and iOS). | |||||
This will attempt to create a new midi output device with the specified name that other | |||||
apps can connect to and use as their midi input. | |||||
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability" | |||||
setting in the iOS exporter otherwise this method will fail. | |||||
Returns an empty object if a device can't be created. | |||||
@param deviceName the name of the device to create | |||||
*/ | |||||
static std::unique_ptr<MidiOutput> createNewDevice (const String& deviceName); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiOutput() override; | |||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||||
/** Returns the identifier of this device. */ | |||||
String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||||
/** Returns the name of this device. */ | |||||
String getName() const noexcept { return deviceInfo.name; } | |||||
/** Sets a custom name for the device. */ | |||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||||
//============================================================================== | |||||
/** Sends out a MIDI message immediately. */ | |||||
void sendMessageNow (const MidiMessage& message); | |||||
/** Sends out a sequence of MIDI messages immediately. */ | |||||
void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||||
/** This lets you supply a block of messages that will be sent out at some point | |||||
in the future. | |||||
The MidiOutput class has an internal thread that can send out timestamped | |||||
messages - this appends a set of messages to its internal buffer, ready for | |||||
sending. | |||||
This will only work if you've already started the thread with startBackgroundThread(). | |||||
A time is specified, at which the block of messages should be sent. This time uses | |||||
the same time base as Time::getMillisecondCounter(), and must be in the future. | |||||
The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||||
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||||
samplesPerSecondForBuffer value is needed to convert this sample position to a | |||||
real time. | |||||
*/ | |||||
void sendBlockOfMessages (const MidiBuffer& buffer, | |||||
double millisecondCounterToStartAt, | |||||
double samplesPerSecondForBuffer); | |||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ | |||||
void clearAllPendingMessages(); | |||||
/** Starts up a background thread so that the device can send blocks of data. | |||||
Call this to get the device ready, before using sendBlockOfMessages(). | |||||
*/ | |||||
void startBackgroundThread(); | |||||
/** Stops the background thread, and clears any pending midi events. | |||||
@see startBackgroundThread | |||||
*/ | |||||
void stopBackgroundThread(); | |||||
/** Returns true if the background thread used to send blocks of data is running. | |||||
@see startBackgroundThread, stopBackgroundThread | |||||
*/ | |||||
bool isBackgroundThreadRunning() const noexcept { return isThreadRunning(); } | |||||
//============================================================================== | |||||
/** Deprecated. */ | |||||
static StringArray getDevices(); | |||||
/** Deprecated. */ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Deprecated. */ | |||||
static std::unique_ptr<MidiOutput> openDevice (int); | |||||
private: | |||||
//============================================================================== | |||||
struct PendingMessage | |||||
{ | |||||
PendingMessage (const void* data, int len, double timeStamp) | |||||
: message (data, len, timeStamp) | |||||
{ | |||||
} | |||||
MidiMessage message; | |||||
PendingMessage* next; | |||||
}; | |||||
//============================================================================== | |||||
explicit MidiOutput (const String&, const String&); | |||||
void run() override; | |||||
MidiDeviceInfo deviceInfo; | |||||
void* internal = nullptr; | |||||
CriticalSection lock; | |||||
PendingMessage* firstMessage = nullptr; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,158 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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) | |||||
{ | |||||
const ScopedLock sl (midiCallbackLock); | |||||
jassert (newSampleRate > 0); | |||||
#if JUCE_DEBUG | |||||
hasCalledReset = true; | |||||
#endif | |||||
sampleRate = newSampleRate; | |||||
incomingMessages.clear(); | |||||
lastCallbackTime = Time::getMillisecondCounterHiRes(); | |||||
} | |||||
void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) | |||||
{ | |||||
const ScopedLock sl (midiCallbackLock); | |||||
#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); | |||||
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) | |||||
{ | |||||
const ScopedLock sl (midiCallbackLock); | |||||
#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; | |||||
lastCallbackTime = timeNow; | |||||
if (! incomingMessages.isEmpty()) | |||||
{ | |||||
int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); | |||||
int startSample = 0; | |||||
int scale = 1 << 16; | |||||
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; | |||||
auto iter = incomingMessages.cbegin(); | |||||
if (numSourceSamples > maxBlockLengthToUse) | |||||
{ | |||||
startSample = numSourceSamples - maxBlockLengthToUse; | |||||
numSourceSamples = maxBlockLengthToUse; | |||||
iter = incomingMessages.findNextSamplePosition (startSample); | |||||
} | |||||
scale = (numSamples << 10) / numSourceSamples; | |||||
std::for_each (iter, incomingMessages.cend(), [&] (const MidiMessageMetadata& meta) | |||||
{ | |||||
const auto pos = ((meta.samplePosition - startSample) * scale) >> 10; | |||||
destBuffer.addEvent (meta.data, meta.numBytes, jlimit (0, numSamples - 1, pos)); | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
// if our event list is shorter than the number we need, put them | |||||
// towards the end of the buffer | |||||
startSample = numSamples - numSourceSamples; | |||||
for (const auto metadata : incomingMessages) | |||||
destBuffer.addEvent (metadata.data, metadata.numBytes, | |||||
jlimit (0, numSamples - 1, metadata.samplePosition + startSample)); | |||||
} | |||||
incomingMessages.clear(); | |||||
} | |||||
} | |||||
void MidiMessageCollector::ensureStorageAllocated (size_t bytes) | |||||
{ | |||||
incomingMessages.ensureSize (bytes); | |||||
} | |||||
//============================================================================== | |||||
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 |
@@ -0,0 +1,113 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 MidiKeyboardState::Listener 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 MidiKeyboardState::Listener, | |||||
public MidiInputCallback | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a MidiMessageCollector. */ | |||||
MidiMessageCollector(); | |||||
/** Destructor. */ | |||||
~MidiMessageCollector() override; | |||||
//============================================================================== | |||||
/** 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); | |||||
/** Preallocates storage for collected messages. | |||||
This can be called before audio processing begins to ensure that there | |||||
is sufficient space for the expected MIDI messages, in order to avoid | |||||
allocations within the audio callback. | |||||
*/ | |||||
void ensureStorageAllocated (size_t bytes); | |||||
//============================================================================== | |||||
/** @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 |
@@ -0,0 +1,188 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Helper class that takes chunks of incoming midi bytes, packages them into | |||||
messages, and dispatches them to a midi callback. | |||||
@tags{Audio} | |||||
*/ | |||||
class MidiDataConcatenator | |||||
{ | |||||
public: | |||||
MidiDataConcatenator (int initialBufferSize) | |||||
: pendingSysexData ((size_t) initialBufferSize) | |||||
{ | |||||
} | |||||
void reset() | |||||
{ | |||||
currentMessageLen = 0; | |||||
pendingSysexSize = 0; | |||||
pendingSysexTime = 0; | |||||
} | |||||
template <typename UserDataType, typename CallbackType> | |||||
void pushMidiData (const void* inputData, int numBytes, double time, | |||||
UserDataType* input, CallbackType& callback) | |||||
{ | |||||
auto d = static_cast<const uint8*> (inputData); | |||||
while (numBytes > 0) | |||||
{ | |||||
auto nextByte = *d; | |||||
if (pendingSysexSize != 0 || nextByte == 0xf0) | |||||
{ | |||||
processSysex (d, numBytes, time, input, callback); | |||||
currentMessageLen = 0; | |||||
continue; | |||||
} | |||||
++d; | |||||
--numBytes; | |||||
if (isRealtimeMessage (nextByte)) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time)); | |||||
// These can be embedded in the middle of a normal message, so we won't | |||||
// reset the currentMessageLen here. | |||||
continue; | |||||
} | |||||
if (isInitialByte (nextByte)) | |||||
{ | |||||
currentMessage[0] = nextByte; | |||||
currentMessageLen = 1; | |||||
} | |||||
else if (currentMessageLen > 0 && currentMessageLen < 3) | |||||
{ | |||||
currentMessage[currentMessageLen++] = nextByte; | |||||
} | |||||
else | |||||
{ | |||||
// message is too long or invalid MIDI - abandon it and start again with the next byte | |||||
currentMessageLen = 0; | |||||
continue; | |||||
} | |||||
auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]); | |||||
if (expectedLength == currentMessageLen) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time)); | |||||
currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
template <typename UserDataType, typename CallbackType> | |||||
void processSysex (const uint8*& d, int& numBytes, double time, | |||||
UserDataType* input, CallbackType& callback) | |||||
{ | |||||
if (*d == 0xf0) | |||||
{ | |||||
pendingSysexSize = 0; | |||||
pendingSysexTime = time; | |||||
} | |||||
pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false); | |||||
auto totalMessage = static_cast<uint8*> (pendingSysexData.getData()); | |||||
auto dest = totalMessage + pendingSysexSize; | |||||
do | |||||
{ | |||||
if (pendingSysexSize > 0 && isStatusByte (*d)) | |||||
{ | |||||
if (*d == 0xf7) | |||||
{ | |||||
*dest++ = *d++; | |||||
++pendingSysexSize; | |||||
--numBytes; | |||||
break; | |||||
} | |||||
if (*d >= 0xfa || *d == 0xf8) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); | |||||
++d; | |||||
--numBytes; | |||||
} | |||||
else | |||||
{ | |||||
pendingSysexSize = 0; | |||||
int used = 0; | |||||
const MidiMessage m (d, numBytes, used, 0, time); | |||||
if (used > 0) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, m); | |||||
numBytes -= used; | |||||
d += used; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
*dest++ = *d++; | |||||
++pendingSysexSize; | |||||
--numBytes; | |||||
} | |||||
} | |||||
while (numBytes > 0); | |||||
if (pendingSysexSize > 0) | |||||
{ | |||||
if (totalMessage [pendingSysexSize - 1] == 0xf7) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime)); | |||||
pendingSysexSize = 0; | |||||
} | |||||
else | |||||
{ | |||||
callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime); | |||||
} | |||||
} | |||||
} | |||||
static bool isRealtimeMessage (uint8 byte) { return byte >= 0xf8 && byte <= 0xfe; } | |||||
static bool isStatusByte (uint8 byte) { return byte >= 0x80; } | |||||
static bool isInitialByte (uint8 byte) { return isStatusByte (byte) && byte != 0xf7; } | |||||
uint8 currentMessage[3]; | |||||
int currentMessageLen = 0; | |||||
MemoryBlock pendingSysexData; | |||||
double pendingSysexTime = 0; | |||||
int pendingSysexSize = 0; | |||||
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,496 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||||
STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ | |||||
METHOD (constructor, "<init>", "(IIIIII)V") \ | |||||
METHOD (getState, "getState", "()I") \ | |||||
METHOD (play, "play", "()V") \ | |||||
METHOD (stop, "stop", "()V") \ | |||||
METHOD (release, "release", "()V") \ | |||||
METHOD (flush, "flush", "()V") \ | |||||
METHOD (write, "write", "([SII)I") \ | |||||
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack") | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||||
METHOD (constructor, "<init>", "(IIIII)V") \ | |||||
METHOD (getState, "getState", "()I") \ | |||||
METHOD (startRecording, "startRecording", "()V") \ | |||||
METHOD (stop, "stop", "()V") \ | |||||
METHOD (read, "read", "([SII)I") \ | |||||
METHOD (release, "release", "()V") \ | |||||
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord") | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
CHANNEL_OUT_STEREO = 12, | |||||
CHANNEL_IN_STEREO = 12, | |||||
CHANNEL_IN_MONO = 16, | |||||
ENCODING_PCM_16BIT = 2, | |||||
STREAM_MUSIC = 3, | |||||
MODE_STREAM = 1, | |||||
STATE_UNINITIALIZED = 0 | |||||
}; | |||||
const char* const javaAudioTypeName = "Android Audio"; | |||||
//============================================================================== | |||||
class AndroidAudioIODevice : public AudioIODevice, | |||||
public Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
AndroidAudioIODevice (const String& deviceName) | |||||
: AudioIODevice (deviceName, javaAudioTypeName), | |||||
Thread ("audio"), | |||||
minBufferSizeOut (0), minBufferSizeIn (0), callback (nullptr), sampleRate (0), | |||||
numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), | |||||
numClientOutputChannels (0), numDeviceOutputChannels (0), | |||||
actualBufferSize (0), isRunning (false), | |||||
inputChannelBuffer (1, 1), | |||||
outputChannelBuffer (1, 1) | |||||
{ | |||||
JNIEnv* env = getEnv(); | |||||
sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); | |||||
minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); | |||||
minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); | |||||
if (minBufferSizeIn <= 0) | |||||
{ | |||||
minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); | |||||
if (minBufferSizeIn > 0) | |||||
numDeviceInputChannelsAvailable = 1; | |||||
else | |||||
numDeviceInputChannelsAvailable = 0; | |||||
} | |||||
DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " | |||||
<< sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); | |||||
} | |||||
~AndroidAudioIODevice() override | |||||
{ | |||||
close(); | |||||
} | |||||
StringArray getOutputChannelNames() override | |||||
{ | |||||
StringArray s; | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
return s; | |||||
} | |||||
StringArray getInputChannelNames() override | |||||
{ | |||||
StringArray s; | |||||
if (numDeviceInputChannelsAvailable == 2) | |||||
{ | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
} | |||||
else if (numDeviceInputChannelsAvailable == 1) | |||||
{ | |||||
s.add ("Audio Input"); | |||||
} | |||||
return s; | |||||
} | |||||
Array<double> getAvailableSampleRates() override | |||||
{ | |||||
Array<double> r; | |||||
r.add ((double) sampleRate); | |||||
return r; | |||||
} | |||||
Array<int> getAvailableBufferSizes() override | |||||
{ | |||||
Array<int> b; | |||||
int n = 16; | |||||
for (int i = 0; i < 50; ++i) | |||||
{ | |||||
b.add (n); | |||||
n += n < 64 ? 16 | |||||
: (n < 512 ? 32 | |||||
: (n < 1024 ? 64 | |||||
: (n < 2048 ? 128 : 256))); | |||||
} | |||||
return b; | |||||
} | |||||
int getDefaultBufferSize() override { return 2048; } | |||||
String open (const BigInteger& inputChannels, | |||||
const BigInteger& outputChannels, | |||||
double requestedSampleRate, | |||||
int bufferSize) override | |||||
{ | |||||
close(); | |||||
if (sampleRate != (int) requestedSampleRate) | |||||
return "Sample rate not allowed"; | |||||
lastError.clear(); | |||||
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||||
numDeviceInputChannels = 0; | |||||
numDeviceOutputChannels = 0; | |||||
activeOutputChans = outputChannels; | |||||
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||||
numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||||
activeInputChans = inputChannels; | |||||
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||||
numClientInputChannels = activeInputChans.countNumberOfSetBits(); | |||||
actualBufferSize = preferredBufferSize; | |||||
inputChannelBuffer.setSize (2, actualBufferSize); | |||||
inputChannelBuffer.clear(); | |||||
outputChannelBuffer.setSize (2, actualBufferSize); | |||||
outputChannelBuffer.clear(); | |||||
JNIEnv* env = getEnv(); | |||||
if (numClientOutputChannels > 0) | |||||
{ | |||||
numDeviceOutputChannels = 2; | |||||
outputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioTrack, AudioTrack.constructor, | |||||
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, | |||||
(jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM))); | |||||
const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24); | |||||
getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr; | |||||
int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); | |||||
if (outputDeviceState > 0) | |||||
{ | |||||
isRunning = true; | |||||
} | |||||
else | |||||
{ | |||||
// failed to open the device | |||||
outputDevice.clear(); | |||||
lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); | |||||
} | |||||
} | |||||
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) | |||||
{ | |||||
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) | |||||
{ | |||||
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio | |||||
// before trying to open an audio input device. This is not going to work! | |||||
jassertfalse; | |||||
inputDevice.clear(); | |||||
lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; | |||||
} | |||||
else | |||||
{ | |||||
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||||
inputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioRecord, AudioRecord.constructor, | |||||
0 /* (default audio source) */, sampleRate, | |||||
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||||
ENCODING_PCM_16BIT, | |||||
(jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16)))))); | |||||
int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); | |||||
if (inputDeviceState > 0) | |||||
{ | |||||
isRunning = true; | |||||
} | |||||
else | |||||
{ | |||||
// failed to open the device | |||||
inputDevice.clear(); | |||||
lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); | |||||
} | |||||
} | |||||
} | |||||
if (isRunning) | |||||
{ | |||||
if (outputDevice != nullptr) | |||||
env->CallVoidMethod (outputDevice, AudioTrack.play); | |||||
if (inputDevice != nullptr) | |||||
env->CallVoidMethod (inputDevice, AudioRecord.startRecording); | |||||
startThread (8); | |||||
} | |||||
else | |||||
{ | |||||
closeDevices(); | |||||
} | |||||
return lastError; | |||||
} | |||||
void close() override | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
stopThread (2000); | |||||
isRunning = false; | |||||
closeDevices(); | |||||
} | |||||
} | |||||
int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } | |||||
int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } | |||||
bool isOpen() override { return isRunning; } | |||||
int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||||
int getCurrentBitDepth() override { return 16; } | |||||
double getCurrentSampleRate() override { return sampleRate; } | |||||
BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||||
BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||||
String getLastError() override { return lastError; } | |||||
bool isPlaying() override { return isRunning && callback != nullptr; } | |||||
int getXRunCount() const noexcept override | |||||
{ | |||||
if (outputDevice != nullptr && getUnderrunCount != nullptr) | |||||
return getEnv()->CallIntMethod (outputDevice, getUnderrunCount); | |||||
return -1; | |||||
} | |||||
void start (AudioIODeviceCallback* newCallback) override | |||||
{ | |||||
if (isRunning && callback != newCallback) | |||||
{ | |||||
if (newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
const ScopedLock sl (callbackLock); | |||||
callback = newCallback; | |||||
} | |||||
} | |||||
void stop() override | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
AudioIODeviceCallback* lastCallback; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
lastCallback = callback; | |||||
callback = nullptr; | |||||
} | |||||
if (lastCallback != nullptr) | |||||
lastCallback->audioDeviceStopped(); | |||||
} | |||||
} | |||||
void run() override | |||||
{ | |||||
JNIEnv* env = getEnv(); | |||||
jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); | |||||
while (! threadShouldExit()) | |||||
{ | |||||
if (inputDevice != nullptr) | |||||
{ | |||||
jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); | |||||
if (numRead < actualBufferSize * numDeviceInputChannels) | |||||
{ | |||||
DBG ("Audio read under-run! " << numRead); | |||||
} | |||||
jshort* const src = env->GetShortArrayElements (audioBuffer, nullptr); | |||||
for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan) | |||||
{ | |||||
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan)); | |||||
if (chan < numDeviceInputChannels) | |||||
{ | |||||
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels); | |||||
d.convertSamples (s, actualBufferSize); | |||||
} | |||||
else | |||||
{ | |||||
d.clearSamples (actualBufferSize); | |||||
} | |||||
} | |||||
env->ReleaseShortArrayElements (audioBuffer, src, 0); | |||||
} | |||||
if (threadShouldExit()) | |||||
break; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
{ | |||||
callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels, | |||||
outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels, | |||||
actualBufferSize); | |||||
} | |||||
else | |||||
{ | |||||
outputChannelBuffer.clear(); | |||||
} | |||||
} | |||||
if (outputDevice != nullptr) | |||||
{ | |||||
if (threadShouldExit()) | |||||
break; | |||||
jshort* const dest = env->GetShortArrayElements (audioBuffer, nullptr); | |||||
for (int chan = 0; chan < numDeviceOutputChannels; ++chan) | |||||
{ | |||||
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels); | |||||
const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||||
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData); | |||||
d.convertSamples (s, actualBufferSize); | |||||
} | |||||
env->ReleaseShortArrayElements (audioBuffer, dest, 0); | |||||
jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); | |||||
if (numWritten < actualBufferSize * numDeviceOutputChannels) | |||||
{ | |||||
DBG ("Audio write underrun! " << numWritten); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
int minBufferSizeOut, minBufferSizeIn; | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection callbackLock; | |||||
AudioIODeviceCallback* callback; | |||||
jint sampleRate; | |||||
int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; | |||||
int numClientOutputChannels, numDeviceOutputChannels; | |||||
int actualBufferSize; | |||||
bool isRunning; | |||||
String lastError; | |||||
BigInteger activeOutputChans, activeInputChans; | |||||
GlobalRef outputDevice, inputDevice; | |||||
AudioBuffer<float> inputChannelBuffer, outputChannelBuffer; | |||||
jmethodID getUnderrunCount = nullptr; | |||||
void closeDevices() | |||||
{ | |||||
if (outputDevice != nullptr) | |||||
{ | |||||
outputDevice.callVoidMethod (AudioTrack.stop); | |||||
outputDevice.callVoidMethod (AudioTrack.release); | |||||
outputDevice.clear(); | |||||
} | |||||
if (inputDevice != nullptr) | |||||
{ | |||||
inputDevice.callVoidMethod (AudioRecord.stop); | |||||
inputDevice.callVoidMethod (AudioRecord.release); | |||||
inputDevice.clear(); | |||||
} | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) | |||||
}; | |||||
//============================================================================== | |||||
class AndroidAudioIODeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} | |||||
//============================================================================== | |||||
void scanForDevices() {} | |||||
StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); } | |||||
int getDefaultDeviceIndex (bool) const { return 0; } | |||||
int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; } | |||||
bool hasSeparateInputsAndOutputs() const { return false; } | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
std::unique_ptr<AndroidAudioIODevice> dev; | |||||
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||||
{ | |||||
dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||||
: inputDeviceName)); | |||||
if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) | |||||
dev = nullptr; | |||||
} | |||||
return dev.release(); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) | |||||
}; | |||||
//============================================================================== | |||||
extern bool isOboeAvailable(); | |||||
extern bool isOpenSLAvailable(); | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() | |||||
{ | |||||
#if JUCE_USE_ANDROID_OBOE | |||||
if (isOboeAvailable()) | |||||
return nullptr; | |||||
#endif | |||||
#if JUCE_USE_ANDROID_OPENSLES | |||||
if (isOpenSLAvailable()) | |||||
return nullptr; | |||||
#endif | |||||
return new AndroidAudioIODeviceType(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,131 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Some shared helpers methods for using the high-performance audio paths on | |||||
Android devices (OpenSL and Oboe). | |||||
@tags{Audio} | |||||
*/ | |||||
namespace AndroidHighPerformanceAudioHelpers | |||||
{ | |||||
//============================================================================== | |||||
static double getNativeSampleRate() | |||||
{ | |||||
return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); | |||||
} | |||||
static int getNativeBufferSizeHint() | |||||
{ | |||||
// This property is a hint of a native buffer size but it does not guarantee the size used. | |||||
auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); | |||||
if (deviceBufferSize == 0) | |||||
return 192; | |||||
return deviceBufferSize; | |||||
} | |||||
static bool isProAudioDevice() | |||||
{ | |||||
static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG") | |||||
&& DynamicLibrary().open ("libapa_jni.so"); | |||||
return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported; | |||||
} | |||||
static bool hasLowLatencyAudioPath() | |||||
{ | |||||
return androidHasSystemFeature ("android.hardware.audio.low_latency"); | |||||
} | |||||
static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate) | |||||
{ | |||||
return ((requestedBufferSize % nativeBufferSize) == 0) | |||||
&& (requestedSampleRate == getNativeSampleRate()) | |||||
&& isProAudioDevice(); | |||||
} | |||||
//============================================================================== | |||||
static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate) | |||||
{ | |||||
if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate)) | |||||
{ | |||||
// see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp | |||||
// "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required | |||||
// for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one | |||||
// is sufficient for lower latency." | |||||
return (getAndroidSDKVersion() >= 18 ? 1 : 2); | |||||
} | |||||
// not using low-latency path so we can use the absolute minimum number of buffers to queue | |||||
return 1; | |||||
} | |||||
static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept | |||||
{ | |||||
auto maxBufferFrames = static_cast<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0)); | |||||
auto maxNumBuffers = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames) | |||||
/ static_cast<double> (nativeBufferSize))); | |||||
return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers); | |||||
} | |||||
static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept | |||||
{ | |||||
static constexpr int maxBufferSizeMs = 200; | |||||
return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate)); | |||||
} | |||||
static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> availableSampleRates) | |||||
{ | |||||
auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate()); | |||||
auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(), | |||||
availableSampleRates.size())); | |||||
Array<int> bufferSizes; | |||||
for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i) | |||||
bufferSizes.add (i * nativeBufferSize); | |||||
return bufferSizes; | |||||
} | |||||
static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate) | |||||
{ | |||||
static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40; | |||||
static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100; | |||||
auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs | |||||
: defaultBufferSizeForStandardLatencyDeviceMs); | |||||
auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate); | |||||
return defaultBuffersToEnqueue * nativeBufferSize; | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,706 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2020 - Raw Material Software Limited | |||||
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 byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23 | |||||
// See juce_core/native/java/README.txt on how to generate this byte-code. | |||||
static const uint8 javaMidiByteCode[] = | |||||
{31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220, | |||||
69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45, | |||||
201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81, | |||||
80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204, | |||||
156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248, | |||||
199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3, | |||||
250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200, | |||||
6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66, | |||||
224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205, | |||||
68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101, | |||||
192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188, | |||||
3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151, | |||||
1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191, | |||||
1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121, | |||||
224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239, | |||||
129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53, | |||||
224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224, | |||||
23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86, | |||||
224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4, | |||||
136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68, | |||||
21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60, | |||||
96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24, | |||||
184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64, | |||||
12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224, | |||||
108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216, | |||||
11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33, | |||||
252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132, | |||||
37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128, | |||||
29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127, | |||||
252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4, | |||||
210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216, | |||||
88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251, | |||||
208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62, | |||||
230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151, | |||||
159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190, | |||||
88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29, | |||||
109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163, | |||||
40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253, | |||||
51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110, | |||||
83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164, | |||||
197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167, | |||||
22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224, | |||||
216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117, | |||||
192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116, | |||||
189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199, | |||||
213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164, | |||||
159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105, | |||||
59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235, | |||||
217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221, | |||||
64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252, | |||||
94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124, | |||||
181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91, | |||||
243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89, | |||||
180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78, | |||||
199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79, | |||||
168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94, | |||||
247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232, | |||||
109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116, | |||||
187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158, | |||||
21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179, | |||||
82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159, | |||||
77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204, | |||||
127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209, | |||||
252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207, | |||||
117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7, | |||||
239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108, | |||||
203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251, | |||||
13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67, | |||||
92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168, | |||||
241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183, | |||||
49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55, | |||||
168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223, | |||||
13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247, | |||||
253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98, | |||||
237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252, | |||||
74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101, | |||||
230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78, | |||||
172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55, | |||||
170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81, | |||||
197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219, | |||||
5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95, | |||||
25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56, | |||||
227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203, | |||||
90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192, | |||||
127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114, | |||||
105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254, | |||||
27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220, | |||||
174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71, | |||||
137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66, | |||||
75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116, | |||||
221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105, | |||||
7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189, | |||||
10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158, | |||||
205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5, | |||||
196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253, | |||||
230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125, | |||||
196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68, | |||||
154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179, | |||||
252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30, | |||||
44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74, | |||||
79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90, | |||||
68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7, | |||||
41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161, | |||||
247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250, | |||||
105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8, | |||||
255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93, | |||||
66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13, | |||||
218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52, | |||||
151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9, | |||||
255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145, | |||||
154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4, | |||||
229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157, | |||||
220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200, | |||||
197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11, | |||||
164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218, | |||||
106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235, | |||||
122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21, | |||||
73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219, | |||||
210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66, | |||||
146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80, | |||||
43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211, | |||||
249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113, | |||||
244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240, | |||||
166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160, | |||||
208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39, | |||||
245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38, | |||||
158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253, | |||||
158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192, | |||||
208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246, | |||||
177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235, | |||||
81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111, | |||||
122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101, | |||||
44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3, | |||||
34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7, | |||||
114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176, | |||||
144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207, | |||||
231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236, | |||||
183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120, | |||||
152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96, | |||||
94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130, | |||||
243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129, | |||||
117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245, | |||||
172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122, | |||||
162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174, | |||||
240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245, | |||||
61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117, | |||||
234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179, | |||||
86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160, | |||||
171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238, | |||||
63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223, | |||||
90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238, | |||||
25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34, | |||||
104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196, | |||||
164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186, | |||||
169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118, | |||||
187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101, | |||||
11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47, | |||||
206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110, | |||||
157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91, | |||||
150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245, | |||||
125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62, | |||||
109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46, | |||||
153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185, | |||||
81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24, | |||||
83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121, | |||||
38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217, | |||||
95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134, | |||||
137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150, | |||||
129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73, | |||||
144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173, | |||||
205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43, | |||||
223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215, | |||||
241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186, | |||||
90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101, | |||||
19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153, | |||||
30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250, | |||||
215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27, | |||||
163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130, | |||||
249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34, | |||||
107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153, | |||||
167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127, | |||||
103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196, | |||||
38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138, | |||||
79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199, | |||||
199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163, | |||||
121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208, | |||||
151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163, | |||||
163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110, | |||||
231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221, | |||||
22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251, | |||||
233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70, | |||||
205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191, | |||||
173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251, | |||||
226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216, | |||||
165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116, | |||||
28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194, | |||||
76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181, | |||||
188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119, | |||||
77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190, | |||||
192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173, | |||||
88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168, | |||||
214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29, | |||||
200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176, | |||||
116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106, | |||||
254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219, | |||||
157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202, | |||||
19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105, | |||||
167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77, | |||||
146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76, | |||||
185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228, | |||||
29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198, | |||||
167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26, | |||||
74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80, | |||||
26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229, | |||||
198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249, | |||||
51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121, | |||||
246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199, | |||||
70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70, | |||||
102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200, | |||||
188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193, | |||||
216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201, | |||||
93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242, | |||||
252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175, | |||||
243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109, | |||||
27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140, | |||||
146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176, | |||||
68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213, | |||||
58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38, | |||||
226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184, | |||||
186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217, | |||||
34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38, | |||||
144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115, | |||||
200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38, | |||||
101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112, | |||||
214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28, | |||||
217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42, | |||||
76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223, | |||||
42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126, | |||||
208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227, | |||||
213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164, | |||||
147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205, | |||||
194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213, | |||||
163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83, | |||||
199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250, | |||||
77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74, | |||||
201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154, | |||||
244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127, | |||||
11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146, | |||||
29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35, | |||||
143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33, | |||||
46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163, | |||||
29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160, | |||||
245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102, | |||||
188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240, | |||||
91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253, | |||||
30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105, | |||||
137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122, | |||||
186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244, | |||||
155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137, | |||||
5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233, | |||||
249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228, | |||||
39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49, | |||||
177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227, | |||||
13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179, | |||||
189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94, | |||||
238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196, | |||||
0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23, | |||||
107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161, | |||||
223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57, | |||||
174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108, | |||||
143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240, | |||||
229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232, | |||||
105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198, | |||||
190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56, | |||||
124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72, | |||||
172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25, | |||||
117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118, | |||||
88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198, | |||||
127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184, | |||||
32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148, | |||||
46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179, | |||||
39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85, | |||||
105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48, | |||||
220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123, | |||||
137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182, | |||||
235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64, | |||||
244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110, | |||||
26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181, | |||||
74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15, | |||||
102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195, | |||||
186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132, | |||||
107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157, | |||||
74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12, | |||||
255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248, | |||||
248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0}; | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ | |||||
STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") | |||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode)) | |||||
#undef JNI_CLASS_MEMBERS | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ | |||||
METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ | |||||
METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ | |||||
METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") | |||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23) | |||||
#undef JNI_CLASS_MEMBERS | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
METHOD (start, "start", "()V") \ | |||||
METHOD (stop, "stop", "()V") \ | |||||
METHOD (close, "close", "()V") \ | |||||
METHOD (sendMidi, "sendMidi", "([BII)V") \ | |||||
METHOD (getName, "getName", "()Ljava/lang/String;") | |||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23) | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
class AndroidMidiInput | |||||
{ | |||||
public: | |||||
AndroidMidiInput (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) | |||||
: juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), | |||||
javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, | |||||
(jint) deviceID, (jlong) this))) | |||||
{ | |||||
} | |||||
~AndroidMidiInput() | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
{ | |||||
getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||||
javaMidiDevice.clear(); | |||||
} | |||||
} | |||||
bool isOpen() const noexcept | |||||
{ | |||||
return javaMidiDevice != nullptr; | |||||
} | |||||
void start() | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
getEnv()->CallVoidMethod (d, JuceMidiPort.start); | |||||
} | |||||
void stop() | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
getEnv()->CallVoidMethod (d, JuceMidiPort.stop); | |||||
callback = nullptr; | |||||
} | |||||
String getName() const noexcept | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); | |||||
return {}; | |||||
} | |||||
void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) | |||||
{ | |||||
auto* env = getEnv(); | |||||
jassert (byteArray != nullptr); | |||||
auto* data = env->GetByteArrayElements (byteArray, nullptr); | |||||
HeapBlock<uint8> buffer (static_cast<size_t> (len)); | |||||
std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len)); | |||||
midiConcatenator.pushMidiData (buffer.get(), | |||||
len, static_cast<double> (timestamp) * 1.0e-9, | |||||
juceMidiInput, *callback); | |||||
env->ReleaseByteArrayElements (byteArray, data, 0); | |||||
} | |||||
static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, | |||||
jint offset, jint len, jlong timestamp) | |||||
{ | |||||
auto* myself = reinterpret_cast<AndroidMidiInput*> (host); | |||||
myself->handleMidi (byteArray, offset, len, timestamp); | |||||
} | |||||
private: | |||||
MidiInput* juceMidiInput; | |||||
MidiInputCallback* callback; | |||||
MidiDataConcatenator midiConcatenator; | |||||
GlobalRef javaMidiDevice; | |||||
}; | |||||
//============================================================================== | |||||
class AndroidMidiOutput | |||||
{ | |||||
public: | |||||
AndroidMidiOutput (const LocalRef<jobject>& midiDevice) | |||||
: javaMidiDevice (midiDevice) | |||||
{ | |||||
} | |||||
~AndroidMidiOutput() | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
{ | |||||
getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||||
javaMidiDevice.clear(); | |||||
} | |||||
} | |||||
void send (jbyteArray byteArray, jint offset, jint len) | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
getEnv()->CallVoidMethod (d, | |||||
JuceMidiPort.sendMidi, | |||||
byteArray, offset, len); | |||||
} | |||||
String getName() const noexcept | |||||
{ | |||||
if (jobject d = javaMidiDevice.get()) | |||||
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); | |||||
return {}; | |||||
} | |||||
private: | |||||
GlobalRef javaMidiDevice; | |||||
}; | |||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" ) | |||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
class AndroidMidiDeviceManager | |||||
{ | |||||
public: | |||||
AndroidMidiDeviceManager() | |||||
: deviceManager (LocalRef<jobject>(getEnv()->CallStaticObjectMethod (JuceMidiSupport, | |||||
JuceMidiSupport.getAndroidMidiDeviceManager, | |||||
getAppContext().get()))) | |||||
{ | |||||
} | |||||
Array<MidiDeviceInfo> getDevices (bool input) | |||||
{ | |||||
if (jobject dm = deviceManager.get()) | |||||
{ | |||||
jobjectArray jDeviceNameAndIDs | |||||
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs | |||||
: MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); | |||||
// Create a local reference as converting this to a JUCE string will call into JNI | |||||
LocalRef<jobjectArray> localDeviceNameAndIDs (jDeviceNameAndIDs); | |||||
auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); | |||||
deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||||
Array<MidiDeviceInfo> devices; | |||||
for (int i = 0; i < deviceNameAndIDs.size(); i += 2) | |||||
devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); | |||||
return devices; | |||||
} | |||||
return {}; | |||||
} | |||||
AndroidMidiInput* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||||
{ | |||||
if (auto dm = deviceManager.get()) | |||||
{ | |||||
std::unique_ptr<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, deviceID, callback, dm)); | |||||
if (androidMidiInput->isOpen()) | |||||
return androidMidiInput.release(); | |||||
} | |||||
return nullptr; | |||||
} | |||||
AndroidMidiOutput* openMidiOutputPortWithID (int deviceID) | |||||
{ | |||||
if (auto dm = deviceManager.get()) | |||||
if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) | |||||
return new AndroidMidiOutput (LocalRef<jobject>(javaMidiPort)); | |||||
return nullptr; | |||||
} | |||||
private: | |||||
GlobalRef deviceManager; | |||||
}; | |||||
//============================================================================== | |||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
AndroidMidiDeviceManager manager; | |||||
return manager.getDevices (true); | |||||
} | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
return getAvailableDevices().getFirst(); | |||||
} | |||||
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||||
{ | |||||
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) | |||||
return {}; | |||||
AndroidMidiDeviceManager manager; | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput ({}, deviceIdentifier)); | |||||
if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) | |||||
{ | |||||
midiInput->internal = port; | |||||
midiInput->setName (port->getName()); | |||||
return midiInput; | |||||
} | |||||
return {}; | |||||
} | |||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return (getAndroidSDKVersion() < 23 ? -1 : 0); | |||||
} | |||||
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier, callback); | |||||
} | |||||
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
: deviceInfo (deviceName, deviceIdentifier) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
delete reinterpret_cast<AndroidMidiInput*> (internal); | |||||
} | |||||
void MidiInput::start() | |||||
{ | |||||
if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||||
mi->start(); | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||||
mi->stop(); | |||||
} | |||||
//============================================================================== | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
AndroidMidiDeviceManager manager; | |||||
return manager.getDevices (false); | |||||
} | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
return getAvailableDevices().getFirst(); | |||||
} | |||||
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier) | |||||
{ | |||||
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) | |||||
return {}; | |||||
AndroidMidiDeviceManager manager; | |||||
if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) | |||||
{ | |||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier)); | |||||
midiOutput->internal = port; | |||||
midiOutput->setName (port->getName()); | |||||
return midiOutput; | |||||
} | |||||
return {}; | |||||
} | |||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
if (getAndroidSDKVersion() < 23) | |||||
return {}; | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return (getAndroidSDKVersion() < 23 ? -1 : 0); | |||||
} | |||||
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier); | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
stopBackgroundThread(); | |||||
delete reinterpret_cast<AndroidMidiOutput*> (internal); | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
if (auto* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal)) | |||||
{ | |||||
auto* env = getEnv(); | |||||
auto messageSize = message.getRawDataSize(); | |||||
LocalRef<jbyteArray> messageContent (env->NewByteArray (messageSize)); | |||||
auto content = messageContent.get(); | |||||
auto* rawBytes = env->GetByteArrayElements (content, nullptr); | |||||
std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize)); | |||||
env->ReleaseByteArrayElements (content, rawBytes, 0); | |||||
androidMidi->send (content, (jint) 0, (jint) messageSize); | |||||
} | |||||
} | |||||
} // namespace juce |