Signed-off-by: falkTX <falktx@falktx.com>pull/115/merge
@@ -0,0 +1,469 @@ | |||
/* | |||
============================================================================== | |||
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_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 | |||
// optional | |||
#ifndef JUCE_MODULE_AVAILABLE_juce_audio_devices | |||
#define JUCE_MODULE_AVAILABLE_juce_audio_devices 0 | |||
#endif | |||
// FIXME needed for some plugins | |||
#define JUCE_MODAL_LOOPS_PERMITTED 1 | |||
//============================================================================= | |||
#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 | |||
#ifdef __ARM_NEON__ | |||
#define JUCE_USE_ARM_NEON 1 | |||
#endif | |||
#ifdef __SSE2__ | |||
#define JUCE_USE_SSE_INTRINSICS 1 | |||
#endif | |||
//============================================================================= | |||
// 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 1 | |||
/** 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 | |||
#if LINUX | |||
#define JUCE_DISABLE_NATIVE_FILECHOOSERS 1 | |||
#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 | |||
#define JUCE_OPENGL3 1 | |||
//============================================================================= | |||
// 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,82 @@ | |||
############################################################################### | |||
if linux_embed | |||
juce60_srcs = [ | |||
'source/modules/juce_audio_basics/juce_audio_basics.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 | |||
juce60_srcs = [ | |||
'source/modules/juce_audio_basics/juce_audio_basics.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 | |||
juce60_devices_srcs = [ | |||
'source/modules/juce_audio_devices/juce_audio_devices.cpp', | |||
] | |||
juce60_extra_cpp_args = [ | |||
'-std=gnu++14', | |||
'-Wno-non-virtual-dtor', | |||
] | |||
if os_windows | |||
juce60_extra_cpp_args += [ | |||
'-D_NATIVE_WCHAR_T_DEFINED', | |||
'-D__wchar_t=wchar_t', | |||
'-Wno-unknown-pragmas', | |||
] | |||
if host_machine.cpu() == 'x86' | |||
juce60_extra_cpp_args += [ | |||
'-mpreferred-stack-boundary=2', | |||
] | |||
endif | |||
endif | |||
lib_juce60 = static_library('juce60', | |||
sources: juce60_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 + juce60_extra_cpp_args, | |||
dependencies: dependencies, | |||
pic: true, | |||
install: false, | |||
) | |||
lib_juce60_device = static_library('juce60-devices', | |||
sources: juce60_devices_srcs, | |||
include_directories: [ | |||
include_directories('.'), | |||
include_directories('source'), | |||
include_directories('source/modules'), | |||
], | |||
cpp_args: build_flags_cpp + juce60_extra_cpp_args + ['-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1'], | |||
dependencies: dependencies_devices, | |||
pic: true, | |||
install: false, | |||
) | |||
############################################################################### |
@@ -0,0 +1,14 @@ | |||
diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
index a19c7b05e..deabba6db 100644 | |||
--- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
+++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
@@ -185,8 +185,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Bela()); | |||
- addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); | |||
+ addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Oboe()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); |
@@ -0,0 +1,80 @@ | |||
diff --git a/modules/juce_audio_formats/codecs/flac/libFLAC/cpu.c b/modules/juce_audio_formats/codecs/flac/libFLAC/cpu.c | |||
index 4eac42e95..74d120e7d 100644 | |||
--- a/modules/juce_audio_formats/codecs/flac/libFLAC/cpu.c | |||
+++ b/modules/juce_audio_formats/codecs/flac/libFLAC/cpu.c | |||
@@ -240,42 +240,7 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) | |||
disable_sse(info); | |||
# endif | |||
#elif defined(__linux__) | |||
- int sse = 0; | |||
- struct sigaction sigill_save; | |||
- struct sigaction sigill_sse; | |||
- sigill_sse.sa_sigaction = sigill_handler_sse_os; | |||
- #ifdef __ANDROID__ | |||
- sigemptyset (&sigill_sse.sa_mask); | |||
- #else | |||
- __sigemptyset(&sigill_sse.sa_mask); | |||
- #endif | |||
- sigill_sse.sa_flags = SA_SIGINFO | SA_RESETHAND; /* SA_RESETHAND just in case our SIGILL return jump breaks, so we don't get stuck in a loop */ | |||
- if(0 == sigaction(SIGILL, &sigill_sse, &sigill_save)) | |||
- { | |||
- /* http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html */ | |||
- /* see sigill_handler_sse_os() for an explanation of the following: */ | |||
- asm volatile ( | |||
- "xorps %%xmm0,%%xmm0\n\t" /* will cause SIGILL if unsupported by OS */ | |||
- "incl %0\n\t" /* SIGILL handler will jump over this */ | |||
- /* landing zone */ | |||
- "nop\n\t" /* SIGILL jump lands here if "inc" is 9 bytes */ | |||
- "nop\n\t" | |||
- "nop\n\t" | |||
- "nop\n\t" | |||
- "nop\n\t" | |||
- "nop\n\t" | |||
- "nop\n\t" /* SIGILL jump lands here if "inc" is 3 bytes (expected) */ | |||
- "nop\n\t" | |||
- "nop" /* SIGILL jump lands here if "inc" is 1 byte */ | |||
- : "=r"(sse) | |||
- : "0"(sse) | |||
- ); | |||
- | |||
- sigaction(SIGILL, &sigill_save, NULL); | |||
- } | |||
- | |||
- if(!sse) | |||
- disable_sse(info); | |||
+ int sse = 1; | |||
#elif defined(_MSC_VER) | |||
__try { | |||
__asm { | |||
@@ -287,30 +252,7 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) | |||
disable_sse(info); | |||
} | |||
#elif defined(__GNUC__) /* MinGW goes here */ | |||
- int sse = 0; | |||
- /* Based on the idea described in Agner Fog's manual "Optimizing subroutines in assembly language" */ | |||
- /* In theory, not guaranteed to detect lack of OS SSE support on some future Intel CPUs, but in practice works (see the aforementioned manual) */ | |||
- if (ia32_fxsr) { | |||
- struct { | |||
- FLAC__uint32 buff[128]; | |||
- } __attribute__((aligned(16))) fxsr; | |||
- FLAC__uint32 old_val, new_val; | |||
- | |||
- asm volatile ("fxsave %0" : "=m" (fxsr) : "m" (fxsr)); | |||
- old_val = fxsr.buff[50]; | |||
- fxsr.buff[50] ^= 0x0013c0de; /* change value in the buffer */ | |||
- asm volatile ("fxrstor %0" : "=m" (fxsr) : "m" (fxsr)); /* try to change SSE register */ | |||
- fxsr.buff[50] = old_val; /* restore old value in the buffer */ | |||
- asm volatile ("fxsave %0 " : "=m" (fxsr) : "m" (fxsr)); /* old value will be overwritten if SSE register was changed */ | |||
- new_val = fxsr.buff[50]; /* == old_val if FXRSTOR didn't change SSE register and (old_val ^ 0x0013c0de) otherwise */ | |||
- fxsr.buff[50] = old_val; /* again restore old value in the buffer */ | |||
- asm volatile ("fxrstor %0" : "=m" (fxsr) : "m" (fxsr)); /* restore old values of registers */ | |||
- | |||
- if ((old_val^new_val) == 0x0013c0de) | |||
- sse = 1; | |||
- } | |||
- if(!sse) | |||
- disable_sse(info); | |||
+ int sse = 1; | |||
#else | |||
/* no way to test, disable to be safe */ | |||
disable_sse(info); |
@@ -0,0 +1,13 @@ | |||
diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.h b/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
index 22ac79483..5c761f659 100644 | |||
--- a/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
+++ b/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
@@ -979,7 +979,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,538 @@ | |||
diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
new file mode 100644 | |||
index 000000000..58179be1a | |||
--- /dev/null | |||
+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
@@ -0,0 +1,532 @@ | |||
+/* | |||
+ ============================================================================== | |||
+ | |||
+ This file is part of the JUCE library. | |||
+ Copyright (c) 2017 - ROLI Ltd. | |||
+ | |||
+ JUCE is an open source library subject to commercial or open-source | |||
+ licensing. | |||
+ | |||
+ By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
+ Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
+ 27th April 2017). | |||
+ | |||
+ End User License Agreement: www.juce.com/juce-5-licence | |||
+ Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
+ | |||
+ Or: You may also use this code under the terms of the GPL v3 (see | |||
+ www.gnu.org/licenses). | |||
+ | |||
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
+ DISCLAIMED. | |||
+ | |||
+ ============================================================================== | |||
+*/ | |||
+ | |||
+#ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||
+#define JUCE_VSTINTERFACE_H_INCLUDED | |||
+ | |||
+using namespace juce; | |||
+ | |||
+#if JUCE_MSVC | |||
+ #define VSTINTERFACECALL __cdecl | |||
+ #pragma pack(push) | |||
+ #pragma pack(8) | |||
+#elif JUCE_MAC || JUCE_IOS | |||
+ #define VSTINTERFACECALL | |||
+ #if JUCE_64BIT | |||
+ #pragma options align=power | |||
+ #else | |||
+ #pragma options align=mac68k | |||
+ #endif | |||
+#else | |||
+ #define VSTINTERFACECALL | |||
+ #pragma pack(push, 8) | |||
+#endif | |||
+ | |||
+const int32 juceVstInterfaceVersion = 2400; | |||
+const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEffectInterface | |||
+{ | |||
+ int32 interfaceIdentifier; | |||
+ pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
+ void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
+ void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); | |||
+ float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); | |||
+ int32 numPrograms; | |||
+ int32 numParameters; | |||
+ int32 numInputChannels; | |||
+ int32 numOutputChannels; | |||
+ int32 flags; | |||
+ pointer_sized_int hostSpace1; | |||
+ pointer_sized_int hostSpace2; | |||
+ int32 latency; | |||
+ int32 deprecated1; | |||
+ int32 deprecated2; | |||
+ float deprecated3; | |||
+ void* effectPointer; | |||
+ void* userPointer; | |||
+ int32 plugInIdentifier; | |||
+ int32 plugInVersion; | |||
+ void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
+ void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); | |||
+ char emptySpace[56]; | |||
+}; | |||
+ | |||
+typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
+ | |||
+enum VstEffectInterfaceFlags | |||
+{ | |||
+ vstEffectFlagHasEditor = 1, | |||
+ vstEffectFlagInplaceAudio = 16, | |||
+ vstEffectFlagDataInChunks = 32, | |||
+ vstEffectFlagIsSynth = 256, | |||
+ vstEffectFlagInplaceDoubleAudio = 4096 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstHostToPlugInOpcodes | |||
+{ | |||
+ plugInOpcodeOpen, | |||
+ plugInOpcodeClose, | |||
+ plugInOpcodeSetCurrentProgram, | |||
+ plugInOpcodeGetCurrentProgram, | |||
+ plugInOpcodeSetCurrentProgramName, | |||
+ plugInOpcodeGetCurrentProgramName, | |||
+ plugInOpcodeGetParameterLabel, | |||
+ plugInOpcodeGetParameterText, | |||
+ plugInOpcodeGetParameterName, | |||
+ plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, | |||
+ plugInOpcodeSetBlockSize, | |||
+ plugInOpcodeResumeSuspend, | |||
+ plugInOpcodeGetEditorBounds, | |||
+ plugInOpcodeOpenEditor, | |||
+ plugInOpcodeCloseEditor, | |||
+ plugInOpcodeDrawEditor, | |||
+ plugInOpcodeGetMouse, | |||
+ plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, | |||
+ plugInOpcodeeffEditorTop, | |||
+ plugInOpcodeSleepEditor, | |||
+ plugInOpcodeIdentify, | |||
+ plugInOpcodeGetData, | |||
+ plugInOpcodeSetData, | |||
+ plugInOpcodePreAudioProcessingEvents, | |||
+ plugInOpcodeIsParameterAutomatable, | |||
+ plugInOpcodeParameterValueForText, | |||
+ plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, | |||
+ plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, | |||
+ plugInOpcodeConnectOutput, | |||
+ plugInOpcodeGetInputPinProperties, | |||
+ plugInOpcodeGetOutputPinProperties, | |||
+ plugInOpcodeGetPlugInCategory, | |||
+ plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, | |||
+ plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, | |||
+ plugInOpcodeGetPlugInName, | |||
+ plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, | |||
+ plugInOpcodeGetManufacturerProductName, | |||
+ plugInOpcodeGetManufacturerVersion, | |||
+ plugInOpcodeManufacturerSpecific, | |||
+ plugInOpcodeCanPlugInDo, | |||
+ plugInOpcodeGetTailSize, | |||
+ plugInOpcodeIdle, | |||
+ plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, | |||
+ plugInOpcodeGetVstInterfaceVersion, | |||
+ plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, | |||
+ plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, | |||
+ plugInOpcodeNextPlugInUniqueID, | |||
+ plugInOpcodeStartProcess, | |||
+ plugInOpcodeStopProcess, | |||
+ plugInOpcodeSetNumberOfSamplesToProcess, | |||
+ plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, | |||
+ pluginOpcodeGetNumMidiInputChannels, | |||
+ pluginOpcodeGetNumMidiOutputChannels, | |||
+ plugInOpcodeMaximum = pluginOpcodeGetNumMidiOutputChannels | |||
+}; | |||
+ | |||
+ | |||
+enum VstPlugInToHostOpcodes | |||
+{ | |||
+ hostOpcodeParameterChanged, | |||
+ hostOpcodeVstVersion, | |||
+ hostOpcodeCurrentId, | |||
+ hostOpcodeIdle, | |||
+ hostOpcodePinConnected, | |||
+ hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, | |||
+ hostOpcodeGetTimingInfo, | |||
+ hostOpcodePreAudioProcessingEvents, | |||
+ hostOpcodeSetTime, | |||
+ hostOpcodeTempoAt, | |||
+ hostOpcodeGetNumberOfAutomatableParameters, | |||
+ hostOpcodeGetParameterInterval, | |||
+ hostOpcodeIOModified, | |||
+ hostOpcodeNeedsIdle, | |||
+ hostOpcodeWindowSize, | |||
+ hostOpcodeGetSampleRate, | |||
+ hostOpcodeGetBlockSize, | |||
+ hostOpcodeGetInputLatency, | |||
+ hostOpcodeGetOutputLatency, | |||
+ hostOpcodeGetPreviousPlugIn, | |||
+ hostOpcodeGetNextPlugIn, | |||
+ hostOpcodeWillReplace, | |||
+ hostOpcodeGetCurrentAudioProcessingLevel, | |||
+ hostOpcodeGetAutomationState, | |||
+ hostOpcodeOfflineStart, | |||
+ hostOpcodeOfflineReadSource, | |||
+ hostOpcodeOfflineWrite, | |||
+ hostOpcodeOfflineGetCurrentPass, | |||
+ hostOpcodeOfflineGetCurrentMetaPass, | |||
+ hostOpcodeSetOutputSampleRate, | |||
+ hostOpcodeGetOutputSpeakerConfiguration, | |||
+ hostOpcodeGetManufacturerName, | |||
+ hostOpcodeGetProductName, | |||
+ hostOpcodeGetManufacturerVersion, | |||
+ hostOpcodeManufacturerSpecific, | |||
+ hostOpcodeSetIcon, | |||
+ hostOpcodeCanHostDo, | |||
+ hostOpcodeGetLanguage, | |||
+ hostOpcodeOpenEditorWindow, | |||
+ hostOpcodeCloseEditorWindow, | |||
+ hostOpcodeGetDirectory, | |||
+ hostOpcodeUpdateView, | |||
+ hostOpcodeParameterChangeGestureBegin, | |||
+ hostOpcodeParameterChangeGestureEnd, | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstProcessingSampleType | |||
+{ | |||
+ vstProcessingSampleTypeFloat, | |||
+ vstProcessingSampleTypeDouble | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+// These names must be identical to the Steinberg SDK so JUCE users can set | |||
+// exactly what they want. | |||
+enum VstPlugInCategory | |||
+{ | |||
+ kPlugCategUnknown, | |||
+ kPlugCategEffect, | |||
+ kPlugCategSynth, | |||
+ kPlugCategAnalysis, | |||
+ kPlugCategMastering, | |||
+ kPlugCategSpacializer, | |||
+ kPlugCategRoomFx, | |||
+ kPlugSurroundFx, | |||
+ kPlugCategRestoration, | |||
+ kPlugCategOfflineProcess, | |||
+ kPlugCategShell, | |||
+ kPlugCategGenerator | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEditorBounds | |||
+{ | |||
+ int16 upper; | |||
+ int16 leftmost; | |||
+ int16 lower; | |||
+ int16 rightmost; | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstMaxStringLengths | |||
+{ | |||
+ vstMaxNameLength = 64, | |||
+ vstMaxParameterOrPinLabelLength = 64, | |||
+ vstMaxParameterOrPinShortLabelLength = 8, | |||
+ vstMaxCategoryLength = 24, | |||
+ vstMaxManufacturerStringLength = 64, | |||
+ vstMaxPlugInNameStringLength = 64 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstPinInfo | |||
+{ | |||
+ char text[vstMaxParameterOrPinLabelLength]; | |||
+ int32 flags; | |||
+ int32 configurationType; | |||
+ char shortText[vstMaxParameterOrPinShortLabelLength]; | |||
+ char unused[48]; | |||
+}; | |||
+ | |||
+enum VstPinInfoFlags | |||
+{ | |||
+ vstPinInfoFlagIsActive = 1, | |||
+ vstPinInfoFlagIsStereo = 2, | |||
+ vstPinInfoFlagValid = 4 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 sampleOffset; | |||
+ int32 flags; | |||
+ char content[16]; | |||
+}; | |||
+ | |||
+enum VstEventTypes | |||
+{ | |||
+ vstMidiEventType = 1, | |||
+ vstSysExEventType = 6 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEventBlock | |||
+{ | |||
+ int32 numberOfEvents; | |||
+ pointer_sized_int future; | |||
+ VstEvent* events[2]; | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstMidiEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 sampleOffset; | |||
+ int32 flags; | |||
+ int32 noteSampleLength; | |||
+ int32 noteSampleOffset; | |||
+ char midiData[4]; | |||
+ char tuning; | |||
+ char noteVelocityOff; | |||
+ char future1; | |||
+ char future2; | |||
+}; | |||
+ | |||
+enum VstMidiEventFlags | |||
+{ | |||
+ vstMidiEventIsRealtime = 1 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstSysExEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 offsetSamples; | |||
+ int32 flags; | |||
+ int32 sysExDumpSize; | |||
+ pointer_sized_int future1; | |||
+ char* sysExDump; | |||
+ pointer_sized_int future2; | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstTimingInformation | |||
+{ | |||
+ double samplePosition; | |||
+ double sampleRate; | |||
+ double systemTimeNanoseconds; | |||
+ double musicalPosition; | |||
+ double tempoBPM; | |||
+ double lastBarPosition; | |||
+ double loopStartPosition; | |||
+ double loopEndPosition; | |||
+ int32 timeSignatureNumerator; | |||
+ int32 timeSignatureDenominator; | |||
+ int32 smpteOffset; | |||
+ int32 smpteRate; | |||
+ int32 samplesToNearestClock; | |||
+ int32 flags; | |||
+}; | |||
+ | |||
+enum VstTimingInformationFlags | |||
+{ | |||
+ vstTimingInfoFlagTransportChanged = 1, | |||
+ vstTimingInfoFlagCurrentlyPlaying = 2, | |||
+ vstTimingInfoFlagLoopActive = 4, | |||
+ vstTimingInfoFlagCurrentlyRecording = 8, | |||
+ vstTimingInfoFlagAutomationWriteModeActive = 64, | |||
+ vstTimingInfoFlagAutomationReadModeActive = 128, | |||
+ vstTimingInfoFlagNanosecondsValid = 256, | |||
+ vstTimingInfoFlagMusicalPositionValid = 512, | |||
+ vstTimingInfoFlagTempoValid = 1024, | |||
+ vstTimingInfoFlagLastBarPositionValid = 2048, | |||
+ vstTimingInfoFlagLoopPositionValid = 4096, | |||
+ vstTimingInfoFlagTimeSignatureValid = 8192, | |||
+ vstTimingInfoFlagSmpteValid = 16384, | |||
+ vstTimingInfoFlagNearestClockValid = 32768 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstSmpteRates | |||
+{ | |||
+ vstSmpteRateFps24, | |||
+ vstSmpteRateFps25, | |||
+ vstSmpteRateFps2997, | |||
+ vstSmpteRateFps30, | |||
+ vstSmpteRateFps2997drop, | |||
+ vstSmpteRateFps30drop, | |||
+ | |||
+ vstSmpteRate16mmFilm, | |||
+ vstSmpteRate35mmFilm, | |||
+ | |||
+ vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, | |||
+ vstSmpteRateFps249 , | |||
+ vstSmpteRateFps599, | |||
+ vstSmpteRateFps60 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstIndividualSpeakerInfo | |||
+{ | |||
+ float azimuthalAngle; | |||
+ float elevationAngle; | |||
+ float radius; | |||
+ float reserved; | |||
+ char label[vstMaxNameLength]; | |||
+ int32 type; | |||
+ char unused[28]; | |||
+}; | |||
+ | |||
+enum VstIndividualSpeakerType | |||
+{ | |||
+ vstIndividualSpeakerTypeUndefined = 0x7fffffff, | |||
+ vstIndividualSpeakerTypeMono = 0, | |||
+ vstIndividualSpeakerTypeLeft, | |||
+ vstIndividualSpeakerTypeRight, | |||
+ vstIndividualSpeakerTypeCentre, | |||
+ vstIndividualSpeakerTypeLFE, | |||
+ vstIndividualSpeakerTypeLeftSurround, | |||
+ vstIndividualSpeakerTypeRightSurround, | |||
+ vstIndividualSpeakerTypeLeftCentre, | |||
+ vstIndividualSpeakerTypeRightCentre, | |||
+ vstIndividualSpeakerTypeSurround, | |||
+ vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, | |||
+ vstIndividualSpeakerTypeLeftRearSurround, | |||
+ vstIndividualSpeakerTypeRightRearSurround, | |||
+ vstIndividualSpeakerTypeTopMiddle, | |||
+ vstIndividualSpeakerTypeTopFrontLeft, | |||
+ vstIndividualSpeakerTypeTopFrontCentre, | |||
+ vstIndividualSpeakerTypeTopFrontRight, | |||
+ vstIndividualSpeakerTypeTopRearLeft, | |||
+ vstIndividualSpeakerTypeTopRearCentre, | |||
+ vstIndividualSpeakerTypeTopRearRight, | |||
+ vstIndividualSpeakerTypeLFE2 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstSpeakerConfiguration | |||
+{ | |||
+ int32 type; | |||
+ int32 numberOfChannels; | |||
+ VstIndividualSpeakerInfo speakers[8]; | |||
+}; | |||
+ | |||
+enum VstSpeakerConfigurationType | |||
+{ | |||
+ vstSpeakerConfigTypeUser = -2, | |||
+ vstSpeakerConfigTypeEmpty = -1, | |||
+ vstSpeakerConfigTypeMono = 0, | |||
+ vstSpeakerConfigTypeLR, | |||
+ vstSpeakerConfigTypeLsRs, | |||
+ vstSpeakerConfigTypeLcRc, | |||
+ vstSpeakerConfigTypeSlSr, | |||
+ vstSpeakerConfigTypeCLfe, | |||
+ vstSpeakerConfigTypeLRC, | |||
+ vstSpeakerConfigTypeLRS, | |||
+ vstSpeakerConfigTypeLRCLfe, | |||
+ vstSpeakerConfigTypeLRLfeS, | |||
+ vstSpeakerConfigTypeLRCS, | |||
+ vstSpeakerConfigTypeLRLsRs, | |||
+ vstSpeakerConfigTypeLRCLfeS, | |||
+ vstSpeakerConfigTypeLRLfeLsRs, | |||
+ vstSpeakerConfigTypeLRCLsRs, | |||
+ vstSpeakerConfigTypeLRCLfeLsRs, | |||
+ vstSpeakerConfigTypeLRCLsRsCs, | |||
+ vstSpeakerConfigTypeLRLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsCs, | |||
+ vstSpeakerConfigTypeLRLfeLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLsRsLcRc, | |||
+ vstSpeakerConfigTypeLRCLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsLcRc, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLsRsLcRcCs, | |||
+ vstSpeakerConfigTypeLRCLsRsCsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 | |||
+}; | |||
+ | |||
+#if JUCE_BIG_ENDIAN | |||
+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) | |||
+#else | |||
+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) | |||
+#endif | |||
+ | |||
+enum PresonusExtensionConstants | |||
+{ | |||
+ presonusVendorID = JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), | |||
+ presonusSetContentScaleFactor = JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's') | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct vst2FxBank | |||
+{ | |||
+ int32 magic1; | |||
+ int32 size; | |||
+ int32 magic2; | |||
+ int32 version1; | |||
+ int32 fxID; | |||
+ int32 version2; | |||
+ int32 elements; | |||
+ int32 current; | |||
+ char shouldBeZero[124]; | |||
+ int32 chunkSize; | |||
+ char chunk[1]; | |||
+}; | |||
+ | |||
+#if JUCE_MSVC | |||
+ #pragma pack(pop) | |||
+#elif JUCE_MAC || JUCE_IOS | |||
+ #pragma options align=reset | |||
+#else | |||
+ #pragma pack(pop) | |||
+#endif | |||
+ | |||
+#endif // JUCE_VSTINTERFACE_H_INCLUDED |
@@ -0,0 +1,58 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index 8683dc728..94f6bf569 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -61,7 +61,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) | |||
#define PRAGMA_ALIGN_SUPPORTED 1 | |||
#endif | |||
-#if ! JUCE_MSVC | |||
+#if ! JUCE_WINDOWS | |||
#define __cdecl | |||
#endif | |||
diff --git a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h | |||
index 6bea84307..baaa59f06 100644 | |||
--- a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h | |||
+++ b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h | |||
@@ -73,11 +73,6 @@ | |||
#define JucePlugin_Build_RTAS 0 | |||
#endif | |||
-#if ! (defined (_MSC_VER) || defined (__APPLE_CPP__) || defined (__APPLE_CC__) || defined (LINUX) || defined (__linux__)) | |||
- #undef JucePlugin_Build_VST3 | |||
- #define JucePlugin_Build_VST3 0 | |||
-#endif | |||
- | |||
//============================================================================== | |||
#if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) | |||
#error "You need to define the JucePlugin_LV2URI value!" | |||
diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
index 620ba9874..d9359b736 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
+++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
@@ -62,9 +62,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
#ifndef WM_APPCOMMAND | |||
#define WM_APPCOMMAND 0x0319 | |||
#endif | |||
- | |||
- extern "C" void _fpreset(); | |||
- extern "C" void _clearfp(); | |||
#elif ! JUCE_WINDOWS | |||
static void _fpreset() {} | |||
static void _clearfp() {} | |||
diff --git a/modules/juce_core/threads/juce_WaitableEvent.h b/modules/juce_core/threads/juce_WaitableEvent.h | |||
index 52269f706..670903653 100644 | |||
--- a/modules/juce_core/threads/juce_WaitableEvent.h | |||
+++ b/modules/juce_core/threads/juce_WaitableEvent.h | |||
@@ -20,6 +20,10 @@ | |||
============================================================================== | |||
*/ | |||
+#if JUCE_MINGW && !defined(_GLIBCXX_HAS_GTHREADS) | |||
+#include "mingw-std-threads/mingw.condition_variable.h" | |||
+#endif | |||
+ | |||
namespace juce | |||
{ | |||
@@ -0,0 +1,307 @@ | |||
diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
index acd165ff0..bdc0bf58f 100644 | |||
--- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
+++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
@@ -20,6 +20,11 @@ | |||
============================================================================== | |||
*/ | |||
+#ifndef __clang__ | |||
+// GCC4 compatibility | |||
+namespace std { using ::max_align_t; } | |||
+#endif | |||
+ | |||
namespace juce | |||
{ | |||
diff --git a/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h b/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h | |||
index ac5ce32da..f0cd938ae 100644 | |||
--- a/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h | |||
+++ b/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h | |||
@@ -236,8 +236,10 @@ private: | |||
{ 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 } }, | |||
+#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
{ 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 } }, | |||
+#endif | |||
{ 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 } }, | |||
diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
index 9aa4a338e..7cad3e7c1 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
+++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
@@ -59,6 +59,10 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
namespace juce | |||
{ | |||
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_8 | |||
+static const uint32 kAudioUnitType_MIDIProcessor = 'aumi'; | |||
+#endif | |||
+ | |||
// Change this to disable logging of various activities | |||
#ifndef AU_LOGGING | |||
#define AU_LOGGING 1 | |||
@@ -276,7 +280,7 @@ namespace AudioUnitFormatHelpers | |||
NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; | |||
NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; | |||
- NSDictionary* dict = audioComponents[0]; | |||
+ NSDictionary* dict = [audioComponents objectAtIndex: 0]; | |||
desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); | |||
desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
index 39eac8211..f556f12b3 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
@@ -1184,7 +1184,7 @@ public: | |||
String xMeterID, yMeterID; | |||
}; | |||
- virtual CurveData getResponseCurve (CurveData::Type /*curveType*/) const { return {}; } | |||
+ virtual CurveData getResponseCurve (CurveData::Type /*curveType*/) const { return CurveData(); } | |||
//============================================================================== | |||
/** Not for public use - this is called before deleting an editor component. */ | |||
diff --git a/modules/juce_core/native/juce_osx_ObjCHelpers.h b/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
index a9a7c8eb7..aa068eaa0 100644 | |||
--- a/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
+++ b/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
@@ -71,7 +71,7 @@ inline NSArray* varArrayToNSArray (const var& varToParse); | |||
inline NSDictionary* varObjectToNSDictionary (const var& varToParse) | |||
{ | |||
- auto dictionary = [NSMutableDictionary dictionary]; | |||
+ NSDictionary* dictionary = [NSMutableDictionary dictionary]; | |||
if (varToParse.isObject()) | |||
{ | |||
@@ -118,7 +118,7 @@ inline NSArray* varArrayToNSArray (const var& varToParse) | |||
const auto* varArray = varToParse.getArray(); | |||
- auto array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()]; | |||
+ NSArray* array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()]; | |||
for (const auto& aVar : *varArray) | |||
{ | |||
@@ -152,7 +152,8 @@ inline var nsDictionaryToVar (NSDictionary* dictionary) | |||
DynamicObject::Ptr dynamicObject (new DynamicObject()); | |||
for (NSString* key in dictionary) | |||
- dynamicObject->setProperty (nsStringToJuce (key), nsObjectToVar (dictionary[key])); | |||
+ dynamicObject->setProperty (nsStringToJuce (key), | |||
+ nsObjectToVar ([dictionary objectForKey: key])); | |||
return var (dynamicObject.get()); | |||
} | |||
diff --git a/modules/juce_core/system/juce_CompilerSupport.h b/modules/juce_core/system/juce_CompilerSupport.h | |||
index 4d96d7cb0..d76d92d49 100644 | |||
--- a/modules/juce_core/system/juce_CompilerSupport.h | |||
+++ b/modules/juce_core/system/juce_CompilerSupport.h | |||
@@ -92,7 +92,7 @@ | |||
//============================================================================== | |||
// C++ library | |||
-#if (defined (__GLIBCXX__) && __GLIBCXX__ < 20130322) || (defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION < 3700)) | |||
+#if (defined (__GLIBCXX__) && __GLIBCXX__ < 20130322) | |||
#error "JUCE requires a C++ library containing std::atomic" | |||
#endif | |||
diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h | |||
index 43000cab6..0a1798ba2 100644 | |||
--- a/modules/juce_core/system/juce_PlatformDefs.h | |||
+++ b/modules/juce_core/system/juce_PlatformDefs.h | |||
@@ -99,11 +99,7 @@ namespace juce | |||
deliberately and want to ignore the warning. | |||
*/ | |||
#if JUCE_CLANG | |||
- #if __has_cpp_attribute(clang::fallthrough) | |||
- #define JUCE_FALLTHROUGH [[clang::fallthrough]]; | |||
- #else | |||
- #define JUCE_FALLTHROUGH | |||
- #endif | |||
+ #define JUCE_FALLTHROUGH [[clang::fallthrough]]; | |||
#elif JUCE_GCC | |||
#if __GNUC__ >= 7 | |||
#define JUCE_FALLTHROUGH [[gnu::fallthrough]]; | |||
diff --git a/modules/juce_core/system/juce_TargetPlatform.h b/modules/juce_core/system/juce_TargetPlatform.h | |||
index 5b4d293bb..6b41688b2 100644 | |||
--- a/modules/juce_core/system/juce_TargetPlatform.h | |||
+++ b/modules/juce_core/system/juce_TargetPlatform.h | |||
@@ -145,8 +145,8 @@ | |||
#endif | |||
#if JUCE_MAC | |||
- #if ! defined (MAC_OS_X_VERSION_10_11) | |||
- #error "The 10.11 SDK (Xcode 7.3.1+) is required to build JUCE apps. You can create apps that run on macOS 10.7+ by changing the deployment target." | |||
+ #if ! defined (MAC_OS_X_VERSION_10_8) | |||
+ #error "The 10.8 SDK is required to build JUCE apps. You can create apps that run on macOS 10.7+ by changing the deployment target." | |||
#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 | |||
#error "Building for OSX 10.6 is no longer supported!" | |||
#endif | |||
diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
index de6ffecfc..2a85d25f5 100644 | |||
--- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
+++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
@@ -26,6 +26,10 @@ | |||
namespace juce | |||
{ | |||
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_8 | |||
+ #define __nullable | |||
+#endif | |||
+ | |||
//============================================================================== | |||
// This class has been renamed from CoreGraphicsImage to avoid a symbol | |||
// collision in Pro Tools 2019.12 and possibly 2020 depending on the Pro Tools | |||
diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
index ff0a23a9f..88142091d 100644 | |||
--- a/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
+++ b/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
@@ -359,20 +359,20 @@ namespace CoreTextTypeLayout | |||
auto verticalJustification = text.getJustification().getOnlyVerticalFlags(); | |||
- auto ctFrameArea = [area, minCTFrameHeight, verticalJustification] | |||
+ const Rectangle<float> ctFrameArea = [area, minCTFrameHeight, verticalJustification] | |||
{ | |||
if (minCTFrameHeight < area.getHeight()) | |||
- return area; | |||
+ return Rectangle<float> (area); | |||
if (verticalJustification == Justification::verticallyCentred) | |||
return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight); | |||
- auto frameArea = area.withHeight (minCTFrameHeight); | |||
+ const Rectangle<float> frameArea = area.withHeight (minCTFrameHeight); | |||
if (verticalJustification == Justification::bottom) | |||
return frameArea.withBottomY (area.getBottom()); | |||
- return frameArea; | |||
+ return Rectangle<float> (frameArea); | |||
}(); | |||
auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), | |||
diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
index 26ca40630..bd5e69ab0 100644 | |||
--- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
+++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
@@ -1583,17 +1583,21 @@ private: | |||
case NSEventTypeSystemDefined: | |||
case NSEventTypeApplicationDefined: | |||
case NSEventTypePeriodic: | |||
+ #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
case NSEventTypeGesture: | |||
+ #endif | |||
case NSEventTypeMagnify: | |||
case NSEventTypeSwipe: | |||
case NSEventTypeRotate: | |||
case NSEventTypeBeginGesture: | |||
case NSEventTypeEndGesture: | |||
case NSEventTypeQuickLook: | |||
- #if JUCE_64BIT | |||
+ #if JUCE_64BIT | |||
case NSEventTypeSmartMagnify: | |||
+ #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
case NSEventTypePressure: | |||
#endif | |||
+ #endif | |||
#if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 | |||
#if JUCE_64BIT | |||
case NSEventTypeDirectTouch: | |||
diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
index 918f0b4a6..63e15d07b 100644 | |||
--- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
+++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
@@ -309,7 +309,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi | |||
for (auto& filename : files) | |||
{ | |||
auto* nsFilename = juceStringToNS (filename); | |||
- auto fileURL = [NSURL fileURLWithPath: nsFilename]; | |||
+ NSURL* fileURL = [NSURL fileURLWithPath: nsFilename]; | |||
auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL]; | |||
auto eventPos = [event locationInWindow]; | |||
diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
index b8e6e0c7e..6ad01c68a 100644 | |||
--- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
+++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
@@ -3329,7 +3329,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XBut | |||
peer->toFront (true); | |||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, getLogicalMousePos (buttonPressEvent, peer->getPlatformScaleFactor()), | |||
ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, | |||
- MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent), {}); | |||
+ MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent)); | |||
} | |||
void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent) const | |||
diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
index 691e5679b..ed4f3c199 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
+++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
@@ -321,7 +321,7 @@ public: | |||
//============================================================================== | |||
void handleMouseEvent (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, ModifierKeys newMods, float pressure, | |||
- float orientation, int64 time, PenDetails pen = {}, int touchIndex = 0); | |||
+ float orientation, int64 time, PenDetails pen = PenDetails(), int touchIndex = 0); | |||
void handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, | |||
int64 time, const MouseWheelDetails&, int touchIndex = 0); | |||
diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp | |||
index 9cf367411..d42555e13 100644 | |||
--- a/modules/juce_gui_extra/juce_gui_extra.cpp | |||
+++ b/modules/juce_gui_extra/juce_gui_extra.cpp | |||
@@ -136,7 +136,9 @@ | |||
#include "misc/juce_PushNotifications.cpp" | |||
#include "misc/juce_RecentlyOpenedFilesList.cpp" | |||
#include "misc/juce_SplashScreen.cpp" | |||
+#if !JUCE_MAC || MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
#include "misc/juce_SystemTrayIconComponent.cpp" | |||
+#endif | |||
#include "misc/juce_LiveConstantEditor.cpp" | |||
#include "misc/juce_AnimatedAppComponent.cpp" | |||
@@ -146,7 +148,9 @@ | |||
#if JUCE_MAC | |||
#include "native/juce_mac_NSViewComponent.mm" | |||
#include "native/juce_mac_AppleRemote.mm" | |||
- #include "native/juce_mac_SystemTrayIcon.cpp" | |||
+ #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
+ #include "native/juce_mac_SystemTrayIcon.cpp" | |||
+ #endif | |||
#endif | |||
#if JUCE_IOS | |||
diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h | |||
index bcec491c9..062bde636 100644 | |||
--- a/modules/juce_gui_extra/juce_gui_extra.h | |||
+++ b/modules/juce_gui_extra/juce_gui_extra.h | |||
@@ -115,7 +115,9 @@ | |||
#include "misc/juce_PushNotifications.h" | |||
#include "misc/juce_RecentlyOpenedFilesList.h" | |||
#include "misc/juce_SplashScreen.h" | |||
+#if !JUCE_MAC || MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
#include "misc/juce_SystemTrayIconComponent.h" | |||
+#endif | |||
#include "misc/juce_WebBrowserComponent.h" | |||
#include "misc/juce_LiveConstantEditor.h" | |||
#include "misc/juce_AnimatedAppComponent.h" | |||
diff --git a/modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.cpp b/modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.cpp | |||
index 9786c0875..34695b5e1 100644 | |||
--- a/modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.cpp | |||
+++ b/modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.cpp | |||
@@ -157,7 +157,7 @@ void RecentlyOpenedFilesList::forgetRecentFileNatively (const File& file) | |||
// from the recent list, so we clear them all and add them back excluding | |||
// the specified file | |||
- auto sharedDocController = [NSDocumentController sharedDocumentController]; | |||
+ NSDocumentController* sharedDocController = [NSDocumentController sharedDocumentController]; | |||
auto recentDocumentURLs = [sharedDocController recentDocumentURLs]; | |||
[sharedDocController clearRecentDocuments: nil]; |
@@ -0,0 +1,13 @@ | |||
diff --git a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp | |||
index 19f1d7ac7..8ffe16b06 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp | |||
+++ b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp | |||
@@ -25,7 +25,7 @@ | |||
#if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX | |||
-#include <ladspa.h> | |||
+#include "ladspa.h" | |||
namespace juce | |||
{ |
@@ -0,0 +1,406 @@ | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp | |||
index 30249c0a5..e6db9f196 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp | |||
@@ -40,8 +40,6 @@ AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : proce | |||
AudioProcessorEditor::~AudioProcessorEditor() | |||
{ | |||
- splashScreen.deleteAndZero(); | |||
- | |||
// if this fails, then the wrapper hasn't called editorBeingDeleted() on the | |||
// filter for some reason.. | |||
jassert (processor.getActiveEditor() != this); | |||
@@ -56,23 +54,6 @@ void AudioProcessorEditor::hostMIDIControllerIsAvailable (bool) { | |||
void AudioProcessorEditor::initialise() | |||
{ | |||
- /* | |||
- ========================================================================== | |||
- In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||
- JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | |||
- ineffective unless you have a JUCE Indie or Pro license, or are using | |||
- JUCE under the GPL v3 license. | |||
- | |||
- End User License Agreement: www.juce.com/juce-6-licence | |||
- ========================================================================== | |||
- */ | |||
- | |||
- // BEGIN SECTION A | |||
- | |||
- splashScreen = new JUCESplashScreen (*this); | |||
- | |||
- // END SECTION A | |||
- | |||
setConstrainer (&defaultConstrainer); | |||
resizeListener.reset (new AudioProcessorEditorListener (*this)); | |||
addComponentListener (resizeListener.get()); | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h | |||
index c925540c9..a472f39e1 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h | |||
@@ -218,7 +218,6 @@ private: | |||
bool resizableByHost = false; | |||
ComponentBoundsConstrainer defaultConstrainer; | |||
ComponentBoundsConstrainer* constrainer = nullptr; | |||
- Component::SafePointer<Component> splashScreen; | |||
AffineTransform hostScaleTransform; | |||
JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | |||
diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp | |||
index 12a815f6b..bda0fd972 100644 | |||
--- a/modules/juce_gui_basics/juce_gui_basics.cpp | |||
+++ b/modules/juce_gui_basics/juce_gui_basics.cpp | |||
@@ -219,7 +219,6 @@ namespace juce | |||
#include "application/juce_Application.cpp" | |||
#include "misc/juce_BubbleComponent.cpp" | |||
#include "misc/juce_DropShadower.cpp" | |||
-#include "misc/juce_JUCESplashScreen.cpp" | |||
#include "layout/juce_FlexBox.cpp" | |||
#include "layout/juce_GridItem.cpp" | |||
diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h | |||
index a9258b86d..def87c2e0 100644 | |||
--- a/modules/juce_gui_basics/juce_gui_basics.h | |||
+++ b/modules/juce_gui_basics/juce_gui_basics.h | |||
@@ -250,7 +250,6 @@ namespace juce | |||
#include "menus/juce_BurgerMenuComponent.h" | |||
#include "buttons/juce_ToolbarButton.h" | |||
#include "misc/juce_DropShadower.h" | |||
-#include "misc/juce_JUCESplashScreen.h" | |||
#include "widgets/juce_TreeView.h" | |||
#include "windows/juce_TopLevelWindow.h" | |||
#include "windows/juce_AlertWindow.h" | |||
diff --git a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp b/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp | |||
deleted file mode 100644 | |||
index 427632661..000000000 | |||
--- a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp | |||
+++ /dev/null | |||
@@ -1,193 +0,0 @@ | |||
-/* | |||
- ============================================================================== | |||
- | |||
- 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 | |||
-{ | |||
- | |||
-/* | |||
- ============================================================================== | |||
- | |||
- In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||
- JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | |||
- ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | |||
- under the GPL v3 license. | |||
- | |||
- End User License Agreement: www.juce.com/juce-6-licence | |||
- | |||
- ============================================================================== | |||
-*/ | |||
- | |||
-// BEGIN SECTION A | |||
- | |||
-#if ! defined (JUCE_DISPLAY_SPLASH_SCREEN) | |||
- #define JUCE_DISPLAY_SPLASH_SCREEN 1 | |||
-#endif | |||
- | |||
-#if ! defined (JUCE_USE_DARK_SPLASH_SCREEN) | |||
- #define JUCE_USE_DARK_SPLASH_SCREEN 1 | |||
-#endif | |||
- | |||
-static const int millisecondsToDisplaySplash = 2000, splashScreenFadeOutTime = 2000; | |||
-static const int splashScreenLogoWidth = 123, splashScreenLogoHeight = 63; | |||
-static uint32 splashDisplayTime = 0; | |||
-static bool splashHasStartedFading = false; | |||
- | |||
-static Rectangle<float> getLogoArea (Rectangle<float> parentRect) | |||
-{ | |||
- return parentRect.reduced (6.0f) | |||
- .removeFromRight ((float) splashScreenLogoWidth) | |||
- .removeFromBottom ((float) splashScreenLogoHeight); | |||
-} | |||
- | |||
-//============================================================================== | |||
-JUCESplashScreen::JUCESplashScreen (Component& parent) | |||
-{ | |||
- ignoreUnused (parent); | |||
- | |||
- #if JUCE_DISPLAY_SPLASH_SCREEN | |||
- if (splashDisplayTime == 0 | |||
- || Time::getMillisecondCounter() < splashDisplayTime + (uint32) millisecondsToDisplaySplash) | |||
- { | |||
- content = getSplashScreenLogo(); | |||
- | |||
- setAlwaysOnTop (true); | |||
- parent.addAndMakeVisible (this); | |||
- } | |||
- else | |||
- #endif | |||
- { | |||
- startTimer (1); | |||
- } | |||
-} | |||
- | |||
-std::unique_ptr<Drawable> JUCESplashScreen::getSplashScreenLogo() | |||
-{ | |||
- const char* svgData = R"JUCESPLASHSCREEN( | |||
- <?xml version="1.0" encoding="UTF-8"?> | |||
- <svg width="123px" height="63px" viewBox="0 0 123 63" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
- <defs> | |||
- <rect id="path-1" x="0.253112033" y="0.253112033" width="122" height="62" rx="10"></rect> | |||
- </defs> | |||
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.8"> | |||
- <mask id="mask-2" fill="white"> | |||
- <use xlink:href="#path-1"></use> | |||
- </mask> | |||
- <rect stroke="#B144C8" stroke-width="2" x="1.25311203" y="1.25311203" width="120" height="60" rx="10" fill=")JUCESPLASHSCREEN" | |||
- #if JUCE_USE_DARK_SPLASH_SCREEN | |||
- "#000000" | |||
- #else | |||
- "#FFFFFF" | |||
- #endif | |||
- "\"></rect>" | |||
- R"JUCESPLASHSCREEN( | |||
- <g id="Slice" mask="url(#mask-2)" fill="#B144C8" transform="translate(6.000000, 14.000000)"> | |||
- <path d="M17.728338,35.4569627 C7.9530089,35.4569627 0,27.5039538 0,17.7286247 C0,7.95303496 7.9530089,2.60592054e-05 17.728338,2.60592054e-05 C27.5039277,2.60592054e-05 35.4569366,7.95303496 35.4569366,17.7286247 C35.4569366,27.5039538 27.5039277,35.4569627 17.728338,35.4569627 Z M17.728338,1.18222797 C8.60474962,1.18222797 1.18230615,8.60493204 1.18230615,17.7285204 C1.18230615,26.8521088 8.60474962,34.2745523 17.728338,34.2745523 C26.852187,34.2745523 34.2746305,26.8521088 34.2746305,17.7285204 C34.2746305,8.60493204 26.852187,1.18222797 17.728338,1.18222797 Z" id="Combined-Shape"></path> | |||
- <path d="M31.7163462,17.1373674 C32.0191542,17.1373674 32.3177926,17.0276581 32.5291328,16.8108455 C32.7621021,16.5724038 32.869466,16.2576086 32.8308984,15.9313473 C32.5035948,13.1505695 31.4109323,10.5157233 29.6709592,8.31215687 C29.4580554,8.04244409 29.1630652,7.89364603 28.8404523,7.89364603 C28.5558858,7.89364603 28.2796582,8.01325778 28.062585,8.23007037 L19.5130809,16.7798351 C19.3812213,16.9119552 19.4745133,17.1373674 19.6610972,17.1373674 L31.7163462,17.1373674 Z" id="Fill-6"></path> | |||
- <path d="M28.8404002,27.5631082 L28.8404002,27.5631082 C29.1630131,27.5631082 29.4580033,27.4148313 29.6711676,27.1451185 C31.4108802,24.941031 32.5038033,22.3064453 32.8308463,19.5256675 C32.8694139,19.1994062 32.76205,18.884611 32.5290807,18.6461693 C32.3177405,18.4293567 32.019102,18.3196475 31.7162941,18.3196475 L19.6610451,18.3196475 C19.4747217,18.3196475 19.3811692,18.5453202 19.5130288,18.6771798 L28.0627935,27.2269445 C28.2796061,27.443757 28.5558336,27.5631082 28.8404002,27.5631082" id="Fill-8"></path> | |||
- <path d="M6.61653649,7.89375026 C6.29392353,7.89375026 5.99893332,8.04228773 5.78576902,8.31200051 C4.04605647,10.5158275 2.95313339,13.1506738 2.62609037,15.9314516 C2.58752274,16.2577128 2.69488667,16.572508 2.92785596,16.8109498 C3.13919612,17.0277623 3.43783461,17.1374716 3.74064258,17.1374716 L15.7958916,17.1374716 C15.9822149,17.1374716 16.0757675,16.9117989 15.9439079,16.7799393 L7.39414318,8.23017461 C7.17733059,8.01336202 6.90110301,7.89375026 6.61653649,7.89375026" id="Fill-10"></path> | |||
- <path d="M15.7957874,18.3197256 L3.74053834,18.3197256 C3.43773038,18.3197256 3.13935247,18.4294349 2.92775173,18.6462475 C2.69504302,18.8846892 2.5874185,19.1994844 2.62598613,19.5257457 C2.95328975,22.3065235 4.04595223,24.9411091 5.78592538,27.1449361 C5.99882908,27.4146489 6.29407988,27.5631864 6.61643225,27.5631864 C6.90073818,27.5631864 7.17670517,27.4440958 7.39351776,27.2278044 L7.39403894,27.2272832 L15.9438036,18.6772579 C16.0756632,18.5451378 15.9823713,18.3197256 15.7957874,18.3197256" id="Fill-12"></path> | |||
- <path d="M17.1374455,3.74079894 C17.1374455,3.43773038 17.0277363,3.13909188 16.8109237,2.92775173 C16.572482,2.69478243 16.2574262,2.5874185 15.9314255,2.62598613 C13.1506477,2.95328975 10.5158015,4.04595223 8.31223504,5.78592538 C8.04252227,5.99882908 7.8937242,6.29407988 7.8937242,6.61643225 C7.8937242,6.90099877 8.01307536,7.17748694 8.23014855,7.39429953 L16.7799132,15.9438036 C16.9117728,16.0756632 17.1374455,15.9823713 17.1374455,15.7957874 L17.1374455,3.74079894 Z" id="Fill-14"></path> | |||
- <path d="M27.5631603,6.61648437 L27.5631603,6.61648437 C27.5631603,6.294132 27.4148834,5.9988812 27.1451707,5.7857169 C24.9410831,4.04600435 22.3064974,2.95308127 19.5257196,2.62603825 C19.1994583,2.58747062 18.8846631,2.69483455 18.6459608,2.92780385 C18.4294088,3.139144 18.3196996,3.43778249 18.3196996,3.74085105 L18.3196996,15.7958395 C18.3196996,15.9821628 18.5453723,16.0757153 18.6772319,15.9438558 L27.2269966,7.39409106 C27.4438092,7.17727847 27.5631603,6.90131148 27.5631603,6.61648437" id="Fill-16"></path> | |||
- <path d="M7.89380238,28.840348 C7.89380238,29.162961 8.04233985,29.4579512 8.31205263,29.6711155 C10.5158796,31.4110887 13.1507259,32.5040117 15.9315037,32.8307942 C16.2575044,32.8693618 16.5725601,32.7619979 16.8110019,32.5290286 C17.0278145,32.3176884 17.1375237,32.0190499 17.1375237,31.7162419 L17.1375237,19.6609929 C17.1375237,19.4746696 16.9115904,19.3811171 16.7799914,19.5129766 L8.23022672,28.0627413 C8.01315354,28.2798145 7.89380238,28.5557815 7.89380238,28.840348" id="Fill-18"></path> | |||
- <path d="M18.3197778,31.7163462 C18.3197778,32.0191542 18.429487,32.3175321 18.646039,32.5291328 C18.8847413,32.7618415 19.1995365,32.869466 19.5257978,32.8308984 C22.3065756,32.5038554 24.9411613,31.4111929 27.1449883,29.6709592 C27.414701,29.4580554 27.5632385,29.1630652 27.5632385,28.8404523 C27.5632385,28.5561463 27.4441479,28.2801794 27.2278565,28.0633668 L18.6773101,19.5133415 C18.5451899,19.3812213 18.3197778,19.4745133 18.3197778,19.6613578 L18.3197778,31.7163462 Z" id="Fill-20"></path> | |||
- </g> | |||
- <path d="M57.404,39.48 L57.404,25.688 L53.404,25.688 L53.404,39.48 C53.404,41.912 51.996,43.512 49.884,43.512 C48.7,43.512 47.804,43.032 46.524,41.336 L43.644,43.832 C45.596,46.392 47.484,47.256 49.884,47.256 C54.268,47.256 57.404,44.152 57.404,39.48 Z M78.508,38.296 L78.508,25.688 L74.508,25.688 L74.508,38.136 C74.508,41.4 72.588,43.512 69.836,43.512 C67.084,43.512 65.164,41.4 65.164,38.136 L65.164,25.688 L61.164,25.688 L61.164,38.296 C61.164,43.864 65.196,47.256 69.836,47.256 C74.476,47.256 78.508,43.864 78.508,38.296 Z M99.324,44.6 L96.956,41.624 C94.94,43.032 93.628,43.512 92.06,43.512 C88.188,43.512 85.052,40.376 85.052,36.344 C85.052,32.312 88.188,29.176 92.06,29.176 C93.692,29.176 94.876,29.592 96.988,31.064 L99.324,28.152 C97.116,26.36 94.748,25.432 92.06,25.432 C86.012,25.432 80.956,30.264 80.956,36.344 C80.956,42.392 85.948,47.256 92.06,47.256 C94.524,47.256 96.604,46.648 99.324,44.6 Z M114.796,47 L114.796,43.384 L105.516,43.384 L105.516,38.04 L114.444,38.04 L114.444,34.424 L105.516,34.424 L105.516,29.304 L114.796,29.304 L114.796,25.688 L101.516,25.688 L101.516,47 L114.796,47 Z" id="JUCE" fill="#B144C8" mask="url(#mask-2)"></path> | |||
- <path d="M66.1199999,21 L63.8399999,14.12 L61.8399999,19.17 L59.8399999,14.12 L57.5599999,21 L58.3999999,21 L59.9099999,16.34 L61.8399999,21.22 L63.7699999,16.34 L65.2799999,21 L66.1199999,21 Z M71.2166666,21 L70.4166666,21 C70.2866666,20.77 70.2466666,20.43 70.2466666,20.18 C69.8966666,20.63 69.2066666,21.08 68.4266666,21.08 C67.4766666,21.08 66.8066666,20.56 66.8066666,19.73 C66.8066666,18.89 67.3666666,18.06 70.1766666,17.61 C70.1766666,17.04 69.7466666,16.66 69.0066666,16.66 C68.3666666,16.66 67.9366666,16.89 67.5566666,17.18 L67.0766666,16.61 C67.6066666,16.17 68.2866666,15.9 69.0666666,15.9 C69.9066666,15.9 70.9766666,16.22 70.9766666,17.62 L70.9766666,19.83 C70.9766666,20.19 71.0366666,20.65 71.2166666,21 Z M70.1766666,18.73 L70.1766666,18.29 C68.1366666,18.64 67.6066666,19.15 67.6066666,19.73 C67.6066666,20.2 68.1166666,20.4 68.5866666,20.4 C69.3066666,20.4 70.1766666,19.8 70.1766666,18.73 Z M77.0633333,21 L76.2633333,21 L76.2633333,20.18 C75.9433333,20.68 75.2933333,21.08 74.5033333,21.08 C73.1133333,21.08 71.9833333,19.9 71.9833333,18.49 C71.9833333,17.08 73.1133333,15.9 74.5033333,15.9 C75.2933333,15.9 75.9433333,16.3 76.2633333,16.8 L76.2633333,14 L77.0633333,14 L77.0633333,21 Z M76.2633333,18.49 C76.2633333,17.47 75.5133333,16.66 74.5233333,16.66 C73.5333333,16.66 72.7833333,17.47 72.7833333,18.49 C72.7833333,19.51 73.5333333,20.32 74.5233333,20.32 C75.5133333,20.32 76.2633333,19.51 76.2633333,18.49 Z M83.14,18.46 C83.14,18.64 83.12,18.81 83.12,18.81 L78.9,18.81 C79.04,19.66 79.77,20.32 80.66,20.32 C81.18,20.32 81.73,20.09 82.12,19.67 L82.69,20.19 C82.17,20.78 81.43,21.08 80.66,21.08 C79.23,21.08 78.07,19.91 78.07,18.49 C78.07,17.06 79.22,15.9 80.65,15.9 C82.12,15.9 83.14,17.06 83.14,18.46 Z M82.3,18.1 C82.12,17.2 81.52,16.66 80.65,16.66 C79.78,16.66 79.07,17.27 78.91,18.1 L82.3,18.1 Z M93.5633333,15.98 L92.7233333,15.98 L91.4533333,19.36 L89.8233333,15.7 L88.1933333,19.36 L86.9233333,15.98 L86.0833333,15.98 L88.1433333,21.28 L89.8233333,17.44 L91.5033333,21.28 L93.5633333,15.98 Z M95.4,14.46 C95.4,14.16 95.15,13.91 94.85,13.91 C94.55,13.91 94.3,14.16 94.3,14.46 C94.3,14.76 94.55,15.01 94.85,15.01 C95.15,15.01 95.4,14.76 95.4,14.46 Z M95.25,21 L95.25,15.98 L94.45,15.98 L94.45,21 L95.25,21 Z M99.1466667,16.69 L99.1466667,15.98 L97.8066667,15.98 L97.8066667,14.34 L97.0066667,14.34 L97.0066667,15.98 L95.9966667,15.98 L95.9966667,16.69 L97.0066667,16.69 L97.0066667,21 L97.8066667,21 L97.8066667,16.69 L99.1466667,16.69 Z M104.173333,21 L104.173333,18.12 C104.173333,16.7 103.243333,15.9 102.093333,15.9 C101.403333,15.9 100.833333,16.27 100.623333,16.63 L100.623333,14 L99.8233334,14 L99.8233334,21 L100.623333,21 L100.623333,18.17 C100.623333,17.22 101.233333,16.66 102.013333,16.66 C102.793333,16.66 103.373333,17.22 103.373333,18.17 L103.373333,21 L104.173333,21 Z" id="Made-with" fill="#B144C8" mask="url(#mask-2)"></path> | |||
- </g> | |||
- </svg> | |||
- )JUCESPLASHSCREEN"; | |||
- | |||
- auto svgXml = parseXML (svgData); | |||
- jassert (svgXml != nullptr); | |||
- return Drawable::createFromSVG (*svgXml); | |||
-} | |||
- | |||
-void JUCESplashScreen::paint (Graphics& g) | |||
-{ | |||
- auto r = getLocalBounds().toFloat(); | |||
- Point<float> bottomRight (0.9f * r.getWidth(), | |||
- 0.9f * r.getHeight()); | |||
- | |||
- ColourGradient cg (Colour (0x00000000), Line<float> (0.0f, r.getHeight(), r.getWidth(), 0.0f) | |||
- .findNearestPointTo (bottomRight), | |||
- Colour (0xff000000), bottomRight, false); | |||
- cg.addColour (0.25f, Colour (0x10000000)); | |||
- cg.addColour (0.50f, Colour (0x30000000)); | |||
- cg.addColour (0.75f, Colour (0x70000000)); | |||
- g.setFillType (cg); | |||
- g.fillAll(); | |||
- | |||
- content->drawWithin (g, getLogoArea (r), RectanglePlacement::centred, 1.0f); | |||
- | |||
- if (splashDisplayTime == 0) | |||
- splashDisplayTime = Time::getMillisecondCounter(); | |||
- | |||
- if (! isTimerRunning()) | |||
- startTimer (millisecondsToDisplaySplash); | |||
-} | |||
- | |||
-void JUCESplashScreen::timerCallback() | |||
-{ | |||
- #if JUCE_DISPLAY_SPLASH_SCREEN | |||
- if (isVisible() && ! splashHasStartedFading) | |||
- { | |||
- splashHasStartedFading = true; | |||
- fader.animateComponent (this, getBounds(), 0.0f, splashScreenFadeOutTime, false, 0, 0); | |||
- } | |||
- | |||
- if (splashHasStartedFading && ! fader.isAnimating()) | |||
- #endif | |||
- delete this; | |||
-} | |||
- | |||
-void JUCESplashScreen::parentSizeChanged() | |||
-{ | |||
- if (auto* p = getParentComponent()) | |||
- setBounds (p->getLocalBounds().removeFromBottom (splashScreenLogoHeight * 3) | |||
- .removeFromRight (splashScreenLogoWidth * 3)); | |||
-} | |||
- | |||
-void JUCESplashScreen::parentHierarchyChanged() | |||
-{ | |||
- toFront (false); | |||
-} | |||
- | |||
-bool JUCESplashScreen::hitTest (int x, int y) | |||
-{ | |||
- if (! splashHasStartedFading) | |||
- return getLogoArea (getLocalBounds().toFloat()).contains ((float) x, (float) y); | |||
- | |||
- return false; | |||
-} | |||
- | |||
-void JUCESplashScreen::mouseUp (const MouseEvent&) | |||
-{ | |||
- URL juceWebsite ("https://juce.com"); | |||
- juceWebsite.launchInDefaultBrowser(); | |||
-} | |||
- | |||
-// END SECTION A | |||
- | |||
-} // namespace juce | |||
diff --git a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.h b/modules/juce_gui_basics/misc/juce_JUCESplashScreen.h | |||
deleted file mode 100644 | |||
index 3767fc301..000000000 | |||
--- a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.h | |||
+++ /dev/null | |||
@@ -1,74 +0,0 @@ | |||
-/* | |||
- ============================================================================== | |||
- | |||
- 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. | |||
- | |||
- ============================================================================== | |||
-*/ | |||
- | |||
-/* | |||
- ============================================================================== | |||
- | |||
- In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||
- JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | |||
- ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | |||
- under the GPL v3 license. | |||
- | |||
- End User License Agreement: www.juce.com/juce-6-licence | |||
- | |||
- ============================================================================== | |||
-*/ | |||
- | |||
-// BEGIN SECTION A | |||
- | |||
-namespace juce | |||
-{ | |||
- | |||
-/** | |||
- The standard JUCE splash screen component. | |||
- | |||
- @tags{GUI} | |||
-*/ | |||
-class JUCE_API JUCESplashScreen : public Component, | |||
- private Timer, | |||
- private DeletedAtShutdown | |||
-{ | |||
-public: | |||
- JUCESplashScreen (Component& parentToAddTo); | |||
- | |||
- static std::unique_ptr<Drawable> getSplashScreenLogo(); | |||
- | |||
-private: | |||
- void paint (Graphics&) override; | |||
- void timerCallback() override; | |||
- void parentSizeChanged() override; | |||
- void parentHierarchyChanged() override; | |||
- bool hitTest (int, int) override; | |||
- void mouseUp (const MouseEvent&) override; | |||
- | |||
- std::unique_ptr<Drawable> content; | |||
- ComponentAnimator fader; | |||
- | |||
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JUCESplashScreen) | |||
-}; | |||
- | |||
-// END SECTION A | |||
- | |||
-} // namespace juce | |||
diff --git a/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp b/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp | |||
index d2360e5f4..42bd2d9d9 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp | |||
+++ b/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp | |||
@@ -41,8 +41,6 @@ ResizableWindow::ResizableWindow (const String& name, Colour bkgnd, bool shouldA | |||
ResizableWindow::~ResizableWindow() | |||
{ | |||
- splashScreen.deleteAndZero(); | |||
- | |||
// Don't delete or remove the resizer components yourself! They're managed by the | |||
// ResizableWindow, and you should leave them alone! You may have deleted them | |||
// accidentally by careless use of deleteAllChildren()..? | |||
@@ -60,27 +58,6 @@ ResizableWindow::~ResizableWindow() | |||
void ResizableWindow::initialise (const bool shouldAddToDesktop) | |||
{ | |||
- /* | |||
- ========================================================================== | |||
- | |||
- In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||
- JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | |||
- ineffective unless you have a JUCE Indie or Pro license, or are using | |||
- JUCE under the GPL v3 license. | |||
- | |||
- End User License Agreement: www.juce.com/juce-6-licence | |||
- | |||
- ========================================================================== | |||
- */ | |||
- | |||
- // BEGIN SECTION A | |||
- | |||
- #if ! JucePlugin_Build_Standalone | |||
- splashScreen = new JUCESplashScreen (*this); | |||
- #endif | |||
- | |||
- // END SECTION A | |||
- | |||
defaultConstrainer.setMinimumOnscreenAmounts (0x10000, 16, 24, 16); | |||
lastNonFullScreenPos.setBounds (50, 50, 256, 256); | |||
diff --git a/modules/juce_gui_basics/windows/juce_ResizableWindow.h b/modules/juce_gui_basics/windows/juce_ResizableWindow.h | |||
index d67303595..e59f620c1 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ResizableWindow.h | |||
+++ b/modules/juce_gui_basics/windows/juce_ResizableWindow.h | |||
@@ -383,7 +383,7 @@ protected: | |||
private: | |||
//============================================================================== | |||
- Component::SafePointer<Component> contentComponent, splashScreen; | |||
+ Component::SafePointer<Component> contentComponent; | |||
bool ownsContentComponent = false, resizeToFitContent = false, fullscreen = false, canDrag = true, dragStarted = false; | |||
ComponentDragger dragger; | |||
Rectangle<int> lastNonFullScreenPos; |
@@ -0,0 +1,46 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index 082a5697a..c79ca18ea 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -600,12 +600,39 @@ File juce_getExecutableFile() | |||
auto localSymbol = (void*) juce_getExecutableFile; | |||
dladdr (localSymbol, &exeInfo); | |||
- return CharPointer_UTF8 (exeInfo.dli_fname); | |||
+ | |||
+ const CharPointer_UTF8 filename (exeInfo.dli_fname); | |||
+ | |||
+ // if the filename is absolute simply return it | |||
+ if (File::isAbsolutePath (filename)) | |||
+ return filename; | |||
+ | |||
+ // if the filename is relative construct from CWD | |||
+ if (filename[0] == '.') | |||
+ return File::getCurrentWorkingDirectory().getChildFile (filename).getFullPathName(); | |||
+ | |||
+ // filename is abstract, look up in PATH | |||
+ if (const char* const envpath = ::getenv ("PATH")) | |||
+ { | |||
+ StringArray paths (StringArray::fromTokens (envpath, ":", "")); | |||
+ | |||
+ for (int i=paths.size(); --i>=0;) | |||
+ { | |||
+ const File filepath (File (paths[i]).getChildFile (filename)); | |||
+ | |||
+ if (filepath.existsAsFile()) | |||
+ return filepath.getFullPathName(); | |||
+ } | |||
+ } | |||
+ | |||
+ // if we reach this, we failed to find ourselves... | |||
+ jassertfalse; | |||
+ return filename; | |||
} | |||
}; | |||
static String filename = DLAddrReader::getFilename(); | |||
- return File::getCurrentWorkingDirectory().getChildFile (filename); | |||
+ return filename; | |||
} | |||
//============================================================================== |
@@ -0,0 +1,119 @@ | |||
diff --git a/modules/juce_core/native/juce_linux_Files.cpp b/modules/juce_core/native/juce_linux_Files.cpp | |||
index d2a302e3e..3dc4602ce 100644 | |||
--- a/modules/juce_core/native/juce_linux_Files.cpp | |||
+++ b/modules/juce_core/native/juce_linux_Files.cpp | |||
@@ -211,15 +211,21 @@ bool Process::openDocument (const String& fileName, const String& parameters) | |||
const char* const argv[4] = { "/bin/sh", "-c", cmdString.toUTF8(), nullptr }; | |||
- auto cpid = fork(); | |||
+#if JUCE_USE_VFORK | |||
+ const auto cpid = vfork(); | |||
+#else | |||
+ const auto cpid = fork(); | |||
+#endif | |||
if (cpid == 0) | |||
{ | |||
+#if ! JUCE_USE_VFORK | |||
setsid(); | |||
+#endif | |||
// Child process | |||
- execve (argv[0], (char**) argv, environ); | |||
- exit (0); | |||
+ if (execvp (argv[0], (char**) argv) < 0) | |||
+ _exit (0); | |||
} | |||
return cpid >= 0; | |||
diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm | |||
index 1a4d07516..f385a089f 100644 | |||
--- a/modules/juce_core/native/juce_mac_Files.mm | |||
+++ b/modules/juce_core/native/juce_mac_Files.mm | |||
@@ -92,23 +92,22 @@ namespace MacFileHelpers | |||
#else | |||
static bool launchExecutable (const String& pathAndArguments) | |||
{ | |||
+ const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), nullptr }; | |||
+ | |||
+#if JUCE_USE_VFORK | |||
+ const auto cpid = vfork(); | |||
+#else | |||
auto cpid = fork(); | |||
+#endif | |||
if (cpid == 0) | |||
{ | |||
- const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), nullptr }; | |||
- | |||
// Child process | |||
- if (execve (argv[0], (char**) argv, nullptr) < 0) | |||
- exit (0); | |||
- } | |||
- else | |||
- { | |||
- if (cpid < 0) | |||
- return false; | |||
+ if (execvp (argv[0], (char**) argv) < 0) | |||
+ _exit (0); | |||
} | |||
- return true; | |||
+ return cpid >= 0; | |||
} | |||
#endif | |||
} | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index c79ca18ea..ad46cf390 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -1104,7 +1104,18 @@ public: | |||
if (pipe (pipeHandles) == 0) | |||
{ | |||
- auto result = fork(); | |||
+ Array<char*> argv; | |||
+ for (auto& arg : arguments) | |||
+ if (arg.isNotEmpty()) | |||
+ argv.add (const_cast<char*> (arg.toRawUTF8())); | |||
+ | |||
+ argv.add (nullptr); | |||
+ | |||
+#if JUCE_USE_VFORK | |||
+ const pid_t result = vfork(); | |||
+#else | |||
+ const pid_t result = fork(); | |||
+#endif | |||
if (result < 0) | |||
{ | |||
@@ -1113,6 +1124,7 @@ public: | |||
} | |||
else if (result == 0) | |||
{ | |||
+#if ! JUCE_USE_VFORK | |||
// we're the child process.. | |||
close (pipeHandles[0]); // close the read handle | |||
@@ -1127,17 +1139,10 @@ public: | |||
dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO); | |||
close (pipeHandles[1]); | |||
+#endif | |||
- Array<char*> argv; | |||
- | |||
- for (auto& arg : arguments) | |||
- if (arg.isNotEmpty()) | |||
- argv.add (const_cast<char*> (arg.toRawUTF8())); | |||
- | |||
- argv.add (nullptr); | |||
- | |||
- execvp (exe.toRawUTF8(), argv.getRawDataPointer()); | |||
- _exit (-1); | |||
+ if (execvp (exe.toRawUTF8(), argv.getRawDataPointer()) < 0) | |||
+ _exit (-1); | |||
} | |||
else | |||
{ |
@@ -0,0 +1,61 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index ad46cf390..13724ba7c 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -1239,6 +1239,11 @@ public: | |||
return 0; | |||
} | |||
+ int getPID() const noexcept | |||
+ { | |||
+ return childPID; | |||
+ } | |||
+ | |||
int childPID = 0; | |||
int pipeHandle = 0; | |||
int exitCode = -1; | |||
diff --git a/modules/juce_core/native/juce_win32_Threads.cpp b/modules/juce_core/native/juce_win32_Threads.cpp | |||
index 4a4148119..1c38ff2cf 100644 | |||
--- a/modules/juce_core/native/juce_win32_Threads.cpp | |||
+++ b/modules/juce_core/native/juce_win32_Threads.cpp | |||
@@ -477,6 +477,11 @@ public: | |||
return (uint32) exitCode; | |||
} | |||
+ int getPID() const noexcept | |||
+ { | |||
+ return 0; | |||
+ } | |||
+ | |||
bool ok; | |||
private: | |||
diff --git a/modules/juce_core/threads/juce_ChildProcess.cpp b/modules/juce_core/threads/juce_ChildProcess.cpp | |||
index 3b284c25a..31e9c8d94 100644 | |||
--- a/modules/juce_core/threads/juce_ChildProcess.cpp | |||
+++ b/modules/juce_core/threads/juce_ChildProcess.cpp | |||
@@ -81,6 +81,11 @@ String ChildProcess::readAllProcessOutput() | |||
} | |||
+uint32 ChildProcess::getPID() const noexcept | |||
+{ | |||
+ return activeProcess != nullptr ? activeProcess->getPID() : 0; | |||
+} | |||
+ | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
diff --git a/modules/juce_core/threads/juce_ChildProcess.h b/modules/juce_core/threads/juce_ChildProcess.h | |||
index 47a26281b..74dbb11ab 100644 | |||
--- a/modules/juce_core/threads/juce_ChildProcess.h | |||
+++ b/modules/juce_core/threads/juce_ChildProcess.h | |||
@@ -101,6 +101,8 @@ public: | |||
*/ | |||
bool kill(); | |||
+ uint32 getPID() const noexcept; | |||
+ | |||
private: | |||
//============================================================================== | |||
class ActiveProcess; |
@@ -0,0 +1,25 @@ | |||
diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
index 746686bf7..2c8cb3a2b 100644 | |||
--- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
@@ -26,6 +26,7 @@ | |||
namespace juce | |||
{ | |||
+#if JUCE_MODAL_LOOPS_PERMITTED | |||
static bool exeIsAvailable (String executable) | |||
{ | |||
ChildProcess child; | |||
@@ -245,10 +246,11 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | |||
}; | |||
+#endif | |||
bool FileChooser::isPlatformDialogAvailable() | |||
{ | |||
- #if JUCE_DISABLE_NATIVE_FILECHOOSERS | |||
+ #if JUCE_DISABLE_NATIVE_FILECHOOSERS || ! JUCE_MODAL_LOOPS_PERMITTED | |||
return false; | |||
#else | |||
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog"); |
@@ -0,0 +1,70 @@ | |||
diff --git a/modules/juce_gui_basics/windows/juce_DialogWindow.cpp b/modules/juce_gui_basics/windows/juce_DialogWindow.cpp | |||
index ea2675d59..92bf09e16 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_DialogWindow.cpp | |||
+++ b/modules/juce_gui_basics/windows/juce_DialogWindow.cpp | |||
@@ -131,7 +131,8 @@ void DialogWindow::showDialog (const String& dialogTitle, | |||
Colour backgroundColour, | |||
const bool escapeKeyTriggersCloseButton, | |||
const bool resizable, | |||
- const bool useBottomRightCornerResizer) | |||
+ const bool useBottomRightCornerResizer, | |||
+ const bool useNativeTitleBar) | |||
{ | |||
LaunchOptions o; | |||
o.dialogTitle = dialogTitle; | |||
@@ -139,9 +140,9 @@ void DialogWindow::showDialog (const String& dialogTitle, | |||
o.componentToCentreAround = componentToCentreAround; | |||
o.dialogBackgroundColour = backgroundColour; | |||
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | |||
- o.useNativeTitleBar = false; | |||
o.resizable = resizable; | |||
o.useBottomRightCornerResizer = useBottomRightCornerResizer; | |||
+ o.useNativeTitleBar = useNativeTitleBar; | |||
o.launchAsync(); | |||
} | |||
@@ -153,7 +154,8 @@ int DialogWindow::showModalDialog (const String& dialogTitle, | |||
Colour backgroundColour, | |||
const bool escapeKeyTriggersCloseButton, | |||
const bool resizable, | |||
- const bool useBottomRightCornerResizer) | |||
+ const bool useBottomRightCornerResizer, | |||
+ const bool useNativeTitleBar) | |||
{ | |||
LaunchOptions o; | |||
o.dialogTitle = dialogTitle; | |||
@@ -161,9 +163,9 @@ int DialogWindow::showModalDialog (const String& dialogTitle, | |||
o.componentToCentreAround = componentToCentreAround; | |||
o.dialogBackgroundColour = backgroundColour; | |||
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | |||
- o.useNativeTitleBar = false; | |||
o.resizable = resizable; | |||
o.useBottomRightCornerResizer = useBottomRightCornerResizer; | |||
+ o.useNativeTitleBar = useNativeTitleBar; | |||
return o.runModal(); | |||
} | |||
diff --git a/modules/juce_gui_basics/windows/juce_DialogWindow.h b/modules/juce_gui_basics/windows/juce_DialogWindow.h | |||
index a10211525..de9ef2f4b 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_DialogWindow.h | |||
+++ b/modules/juce_gui_basics/windows/juce_DialogWindow.h | |||
@@ -193,7 +193,8 @@ public: | |||
Colour backgroundColour, | |||
bool escapeKeyTriggersCloseButton, | |||
bool shouldBeResizable = false, | |||
- bool useBottomRightCornerResizer = false); | |||
+ bool useBottomRightCornerResizer = false, | |||
+ bool useNativeTitleBar = false); | |||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN | |||
/** Easy way of quickly showing a dialog box containing a given component. | |||
@@ -239,7 +240,8 @@ public: | |||
Colour backgroundColour, | |||
bool escapeKeyTriggersCloseButton, | |||
bool shouldBeResizable = false, | |||
- bool useBottomRightCornerResizer = false); | |||
+ bool useBottomRightCornerResizer = false, | |||
+ bool useNativeTitleBar = false); | |||
#endif | |||
@@ -0,0 +1,23 @@ | |||
diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
index 6ad01c68a..490a3a792 100644 | |||
--- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
+++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
@@ -2804,10 +2804,14 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const | |||
if (atoms.windowType != None) | |||
{ | |||
- auto hint = (styleFlags & ComponentPeer::windowIsTemporary) != 0 | |||
- || ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) | |||
- ? XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO") | |||
- : XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); | |||
+ Atom hint = None; | |||
+ | |||
+ if (styleFlags & ComponentPeer::windowIsTemporary) | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); | |||
+ else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); | |||
+ else | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); | |||
if (hint != None) | |||
xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &hint, 1); |
@@ -0,0 +1,16 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index 13724ba7c..f8892081c 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -989,7 +989,11 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||
if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) | |||
return false; | |||
+ #if JUCE_LINUX | |||
+ policy = priority < 9 ? SCHED_OTHER : SCHED_RR; | |||
+ #else | |||
policy = priority == 0 ? SCHED_OTHER : SCHED_RR; | |||
+ #endif | |||
const int minPriority = sched_get_priority_min (policy); | |||
const int maxPriority = sched_get_priority_max (policy); |
@@ -0,0 +1,24 @@ | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
index ae60fde15..dbad562ef 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
@@ -1116,6 +1116,11 @@ public: | |||
/** This method is called when the layout of the audio processor changes. */ | |||
virtual void processorLayoutsChanged(); | |||
+ //============================================================================== | |||
+ /** LV2 specific calls, saving/restore as string. */ | |||
+ virtual String getStateInformationString () { return String(); } | |||
+ virtual void setStateInformationString (const String&) {} | |||
+ | |||
//============================================================================== | |||
/** Adds a listener that will be called when an aspect of this processor changes. */ | |||
virtual void addListener (AudioProcessorListener* newListener); | |||
@@ -1200,6 +1205,7 @@ public: | |||
wrapperType_AudioUnitv3, | |||
wrapperType_RTAS, | |||
wrapperType_AAX, | |||
+ wrapperType_LV2, | |||
wrapperType_Standalone, | |||
wrapperType_Unity | |||
}; |
@@ -0,0 +1,14 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index 94f6bf569..ddbe6fd65 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -294,9 +294,6 @@ public: | |||
// You must at least have some channels | |||
jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); | |||
- if (processor->isMidiEffect()) | |||
- maxNumInChannels = maxNumOutChannels = 2; | |||
- | |||
#ifdef JucePlugin_PreferredChannelConfigurations | |||
processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024); | |||
#endif |
@@ -0,0 +1,13 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index 7e2122580..cff9b7a88 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -1841,7 +1841,7 @@ private: | |||
pointer_sized_int handleGetPlugInName (VstOpCodeArguments args) | |||
{ | |||
- String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1); | |||
+ String (processor->getName()).copyToUTF8 ((char*) args.ptr, 64 + 1); | |||
return 1; | |||
} | |||
@@ -0,0 +1,17 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index ebc9325ca..cabbbcf13 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -203,11 +203,11 @@ struct SharedMessageThread : public Thread | |||
void run() override | |||
{ | |||
initialiseJuce_GUI(); | |||
- initialised = true; | |||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
XWindowSystem::getInstance(); | |||
+ initialised = true; | |||
while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) | |||
{} |
@@ -0,0 +1,20 @@ | |||
diff --git a/modules/juce_events/messages/juce_MessageManager.h b/modules/juce_events/messages/juce_MessageManager.h | |||
index ae7231101..fff5b9794 100644 | |||
--- a/modules/juce_events/messages/juce_MessageManager.h | |||
+++ b/modules/juce_events/messages/juce_MessageManager.h | |||
@@ -311,6 +311,7 @@ public: | |||
// Internal methods - do not use! | |||
void deliverBroadcastMessage (const String&); | |||
~MessageManager() noexcept; | |||
+ static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); | |||
#endif | |||
private: | |||
@@ -333,7 +334,6 @@ private: | |||
static void* exitModalLoopCallback (void*); | |||
static void doPlatformSpecificInitialisation(); | |||
static void doPlatformSpecificShutdown(); | |||
- static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageManager) | |||
}; |
@@ -0,0 +1,145 @@ | |||
diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp | |||
index 43ad88e1b..149154804 100644 | |||
--- a/modules/juce_audio_processors/juce_audio_processors.cpp | |||
+++ b/modules/juce_audio_processors/juce_audio_processors.cpp | |||
@@ -47,7 +47,7 @@ | |||
#endif | |||
#endif | |||
-#if (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) && JUCE_LINUX | |||
+#if (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) && JUCE_LINUX && ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
#include <X11/Xlib.h> | |||
#include <X11/Xutil.h> | |||
#include <sys/utsname.h> | |||
@@ -230,9 +230,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
#include "format_types/juce_LegacyAudioParameter.cpp" | |||
#include "processors/juce_AudioProcessor.cpp" | |||
#include "processors/juce_AudioPluginInstance.cpp" | |||
-#include "processors/juce_AudioProcessorEditor.cpp" | |||
#include "processors/juce_AudioProcessorGraph.cpp" | |||
-#include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
+#if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
+ #include "processors/juce_AudioProcessorEditor.cpp" | |||
+ #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
+#endif | |||
#include "processors/juce_PluginDescription.cpp" | |||
#include "format_types/juce_LADSPAPluginFormat.cpp" | |||
#include "format_types/juce_VSTPluginFormat.cpp" | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
index 040accf2e..df0b6c67b 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
@@ -51,12 +51,14 @@ AudioProcessor::AudioProcessor (const BusesProperties& ioConfig) | |||
AudioProcessor::~AudioProcessor() | |||
{ | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
{ | |||
const ScopedLock sl (activeEditorLock); | |||
// ooh, nasty - the editor should have been deleted before its AudioProcessor. | |||
jassert (activeEditor == nullptr); | |||
} | |||
+ #endif | |||
#if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | |||
// This will fail if you've called beginParameterChangeGesture() for one | |||
@@ -826,6 +828,7 @@ void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChang | |||
processorLayoutsChanged(); | |||
} | |||
+#if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
//============================================================================== | |||
void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept | |||
{ | |||
@@ -862,6 +865,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||
return ed; | |||
} | |||
+#endif | |||
//============================================================================== | |||
void AudioProcessor::getCurrentProgramStateInformation (juce::MemoryBlock& destData) | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
index f72a4ed3f..849b77d3c 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
@@ -926,6 +926,7 @@ public: | |||
*/ | |||
virtual void setNonRealtime (bool isNonRealtime) noexcept; | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
//============================================================================== | |||
/** Creates the processor's GUI. | |||
@@ -975,6 +976,7 @@ public: | |||
This may call createEditor() internally to create the component. | |||
*/ | |||
AudioProcessorEditor* createEditorIfNeeded(); | |||
+ #endif | |||
//============================================================================== | |||
/** Returns the default number of steps for a parameter. | |||
@@ -1191,9 +1193,11 @@ public: | |||
virtual CurveData getResponseCurve (CurveData::Type /*curveType*/) const { return CurveData(); } | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
//============================================================================== | |||
/** Not for public use - this is called before deleting an editor component. */ | |||
void editorBeingDeleted (AudioProcessorEditor*) noexcept; | |||
+ #endif | |||
/** Flags to indicate the type of plugin context in which a processor is being used. */ | |||
enum WrapperType | |||
@@ -1468,7 +1472,9 @@ private: | |||
//============================================================================== | |||
Array<AudioProcessorListener*> listeners; | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
Component::SafePointer<AudioProcessorEditor> activeEditor; | |||
+ #endif | |||
double currentSampleRate = 0; | |||
int blockSize = 0, latencySamples = 0; | |||
bool suspended = false; | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
index 17c5af30c..3d4a0d428 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
@@ -1519,8 +1519,10 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } | |||
+#if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const { return false; } | |||
AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; } | |||
+#endif | |||
int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } | |||
int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
index a60e67d84..17a121893 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
@@ -355,8 +355,10 @@ public: | |||
bool acceptsMidi() const override; | |||
bool producesMidi() const override; | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
bool hasEditor() const override; | |||
AudioProcessorEditor* createEditor() override; | |||
+ #endif | |||
int getNumPrograms() override; | |||
int getCurrentProgram() override; | |||
@@ -392,8 +394,10 @@ public: | |||
bool acceptsMidi() const override; | |||
bool producesMidi() const override; | |||
+ #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
bool hasEditor() const override { return false; } | |||
AudioProcessorEditor* createEditor() override { return nullptr; } | |||
+ #endif | |||
int getNumPrograms() override { return 0; } | |||
int getCurrentProgram() override { return 0; } | |||
void setCurrentProgram (int) override { } |
@@ -0,0 +1,34 @@ | |||
diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
index 7f7d766f8..c84c10d0b 100755 | |||
--- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
@@ -162,6 +162,7 @@ private: | |||
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } | |||
}; | |||
+ #if JUCE_MSVC | |||
bool showDialog (IFileDialog& dialog, bool async) | |||
{ | |||
FILEOPENDIALOGOPTIONS flags = {}; | |||
@@ -371,6 +372,7 @@ private: | |||
return result; | |||
} | |||
+ #endif | |||
Array<URL> openDialogPreVista (bool async) | |||
{ | |||
@@ -480,11 +482,13 @@ private: | |||
const Remover remover (*this); | |||
+ #if JUCE_MSVC | |||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista | |||
&& customComponent == nullptr) | |||
{ | |||
return openDialogVistaAndUp (async); | |||
} | |||
+ #endif | |||
return openDialogPreVista (async); | |||
} |
@@ -0,0 +1,26 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index cabbbcf13..f722ab36c 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -488,7 +488,7 @@ public: | |||
{ | |||
const int numChannels = jmax (numIn, numOut); | |||
- AudioBuffer<FloatType> chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); | |||
+ juce::AudioBuffer<FloatType> chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); | |||
if (isBypassed) | |||
processor->processBlockBypassed (chans, midiEvents); | |||
diff --git a/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h b/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h | |||
index 4b36f6d64..61dbdeede 100644 | |||
--- a/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h | |||
+++ b/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h | |||
@@ -23,6 +23,8 @@ | |||
============================================================================== | |||
*/ | |||
+#pragma once | |||
+ | |||
#include <juce_audio_plugin_client/juce_audio_plugin_client.h> | |||
#include "juce_CreatePluginFilter.h" | |||
@@ -0,0 +1,117 @@ | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp | |||
index c4404c637..fa8d3ccf5 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.cpp | |||
+++ b/modules/juce_gui_basics/components/juce_Component.cpp | |||
@@ -387,6 +387,10 @@ struct Component::ComponentHelpers | |||
template <typename PointOrRect> | |||
static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) | |||
{ | |||
+ float total_scaling = source->getTotalPixelScaling(); | |||
+ Component* top = nullptr; | |||
+ if (source) | |||
+ top = source->getTopLevelComponent(); | |||
while (source != nullptr) | |||
{ | |||
if (source == target) | |||
@@ -395,6 +399,9 @@ struct Component::ComponentHelpers | |||
if (source->isParentOf (target)) | |||
return convertFromDistantParentSpace (source, *target, p); | |||
+ if (source == top) | |||
+ p /= total_scaling; | |||
+ | |||
p = convertToParentSpace (*source, p); | |||
source = source->getParentComponent(); | |||
} | |||
@@ -1390,13 +1397,14 @@ bool Component::reallyContains (Point<int> point, bool returnTrueIfWithinAChild) | |||
Component* Component::getComponentAt (Point<int> position) | |||
{ | |||
+ Point<int> scale = (position.toFloat() * getPixelScaling()).roundToInt(); | |||
if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) | |||
{ | |||
for (int i = childComponentList.size(); --i >= 0;) | |||
{ | |||
auto* child = childComponentList.getUnchecked(i); | |||
- child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); | |||
+ child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, scale)); | |||
if (child != nullptr) | |||
return child; | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h | |||
index 6b2b0072b..ccb2681fa 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.h | |||
+++ b/modules/juce_gui_basics/components/juce_Component.h | |||
@@ -2284,6 +2284,17 @@ public: | |||
*/ | |||
bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } | |||
+ virtual float getPixelScaling() const { return 1.0f; } | |||
+ float getTotalPixelScaling() const { | |||
+ const Component* component = this; | |||
+ float pixel_scaling = 1.0f; | |||
+ while (component) { | |||
+ pixel_scaling *= component->getPixelScaling(); | |||
+ component = component->getParentComponent(); | |||
+ } | |||
+ return pixel_scaling; | |||
+ } | |||
+ | |||
private: | |||
//============================================================================== | |||
friend class ComponentPeer; | |||
diff --git a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
index a8c2c283a..ddb15b88d 100644 | |||
--- a/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
+++ b/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp | |||
@@ -61,7 +61,7 @@ public: | |||
{ | |||
if (auto* peer = comp.getPeer()) | |||
{ | |||
- pos = peer->globalToLocal (pos); | |||
+ pos = peer->globalToLocal (pos) * comp.getTotalPixelScaling(); | |||
auto& peerComp = peer->getComponent(); | |||
return comp.getLocalPoint (&peerComp, ScalingHelpers::unscaledScreenPosToScaled (peerComp, pos)); | |||
} | |||
diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
index 8d7febd4b..7ec8fbb00 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
+++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp | |||
@@ -474,7 +474,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
if (DragHelpers::isSuitableTarget (info, newTarget)) | |||
{ | |||
dragAndDropTargetComponent = newTarget; | |||
- auto pos = newTarget->getLocalPoint (&component, info.position); | |||
+ auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); | |||
if (DragHelpers::isFileDrag (info)) | |||
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragEnter (info.files, pos.x, pos.y); | |||
@@ -491,7 +491,7 @@ bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info) | |||
if (! DragHelpers::isSuitableTarget (info, newTarget)) | |||
return false; | |||
- auto pos = newTarget->getLocalPoint (&component, info.position); | |||
+ auto pos = newTarget->getLocalPoint (&component, info.position * newTarget->getTotalPixelScaling()); | |||
if (DragHelpers::isFileDrag (info)) | |||
dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragMove (info.files, pos.x, pos.y); | |||
diff --git a/modules/juce_opengl/native/juce_OpenGLExtensions.h b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
index e7eab9dbf..d7039b144 100644 | |||
--- a/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
+++ b/modules/juce_opengl/native/juce_OpenGLExtensions.h | |||
@@ -83,7 +83,13 @@ namespace juce | |||
USE_FUNCTION (glCheckFramebufferStatus, GLenum, (GLenum p1), (p1))\ | |||
USE_FUNCTION (glFramebufferTexture2D, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4, GLint p5), (p1, p2, p3, p4, p5))\ | |||
USE_FUNCTION (glFramebufferRenderbuffer, void, (GLenum p1, GLenum p2, GLenum p3, GLuint p4), (p1, p2, p3, p4))\ | |||
- USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4)) | |||
+ USE_FUNCTION (glGetFramebufferAttachmentParameteriv, void, (GLenum p1, GLenum p2, GLenum p3, GLint* p4), (p1, p2, p3, p4))\ | |||
+ USE_FUNCTION (glTransformFeedbackVaryings, void, (GLuint p1, GLsizei p2, const char **p3, GLenum p4), (p1, p2, p3, p4))\ | |||
+ USE_FUNCTION (glBeginTransformFeedback, void, (GLenum p1), (p1))\ | |||
+ USE_FUNCTION (glEndTransformFeedback, void, (), ())\ | |||
+ USE_FUNCTION (glBindBufferBase, void, (GLenum p1, GLuint p2, GLuint p3), (p1, p2, p3))\ | |||
+ USE_FUNCTION (glMapBufferRange, void*, (GLenum p1, GLintptr p2, GLsizeiptr p3, GLbitfield p4), (p1, p2, p3, p4))\ | |||
+ USE_FUNCTION (glUnmapBuffer, GLboolean, (GLenum p1), (p1)); | |||
/** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. | |||
@see OpenGLExtensionFunctions |
@@ -0,0 +1,457 @@ | |||
diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp | |||
index 7642040bd..ed02448b8 100644 | |||
--- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp | |||
+++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp | |||
@@ -842,6 +842,26 @@ namespace WavFileHelpers | |||
return out.getMemoryBlock(); | |||
} | |||
}; | |||
+ | |||
+ //============================================================================== | |||
+ struct Clm_Chunk | |||
+ { | |||
+ static MemoryBlock createFrom (const StringPairArray& values) | |||
+ { | |||
+ MemoryOutputStream out; | |||
+ auto s = values["clm "]; | |||
+ | |||
+ if (s.isNotEmpty()) | |||
+ { | |||
+ out.writeString (s); | |||
+ | |||
+ if ((out.getDataSize() & 1) != 0) | |||
+ out.writeByte(0); | |||
+ } | |||
+ | |||
+ return out.getMemoryBlock(); | |||
+ } | |||
+ }; | |||
//============================================================================== | |||
namespace AXMLChunk | |||
@@ -1321,6 +1341,7 @@ public: | |||
listInfoChunk = ListInfoChunk::createFrom (metadataValues); | |||
acidChunk = AcidChunk::createFrom (metadataValues); | |||
trckChunk = TracktionChunk::createFrom (metadataValues); | |||
+ clm_Chunk = Clm_Chunk::createFrom (metadataValues); | |||
} | |||
headerPosition = out->getPosition(); | |||
@@ -1383,7 +1404,7 @@ public: | |||
} | |||
private: | |||
- MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk; | |||
+ MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk, clm_Chunk; | |||
uint64 lengthInSamples = 0, bytesWritten = 0; | |||
int64 headerPosition = 0; | |||
bool writeFailed = false; | |||
@@ -1421,6 +1442,7 @@ private: | |||
+ chunkSize (listInfoChunk) | |||
+ chunkSize (acidChunk) | |||
+ chunkSize (trckChunk) | |||
+ + chunkSize (clm_Chunk) | |||
+ (8 + 28)); // (ds64 chunk) | |||
riffChunkSize += (riffChunkSize & 1); | |||
@@ -1503,6 +1525,7 @@ private: | |||
writeChunk (listInfoChunk, chunkName ("LIST")); | |||
writeChunk (acidChunk, chunkName ("acid")); | |||
writeChunk (trckChunk, chunkName ("Trkn")); | |||
+ writeChunk (clm_Chunk, chunkName ("clm ")); | |||
writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); | |||
diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
index f647a89fa..703a1d9fe 100644 | |||
--- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
@@ -2625,6 +2625,9 @@ public: | |||
//============================================================================== | |||
void processParameterChanges (Vst::IParameterChanges& paramChanges) | |||
{ | |||
+ if (juceVST3EditController == nullptr) | |||
+ return; | |||
+ | |||
jassert (pluginInstance != nullptr); | |||
auto numParamsChanged = paramChanges.getParameterCount(); | |||
diff --git a/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp b/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp | |||
index 9af1cef88..312e5f442 100644 | |||
--- a/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp | |||
+++ b/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp | |||
@@ -408,6 +408,7 @@ public: | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
TRANS("Error when trying to open audio device!"), | |||
error); | |||
+ resized(); | |||
} | |||
bool showDeviceControlPanel() | |||
@@ -1133,6 +1134,8 @@ void AudioDeviceSelectorComponent::updateMidiOutput() | |||
deviceManager.setDefaultMidiOutputDevice ({}); | |||
else | |||
deviceManager.setDefaultMidiOutputDevice (currentMidiOutputs[selectedId - 1].identifier); | |||
+ | |||
+ resized(); | |||
} | |||
void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*) | |||
diff --git a/modules/juce_core/native/juce_curl_Network.cpp b/modules/juce_core/native/juce_curl_Network.cpp | |||
index f80b806ab..5d0d685db 100644 | |||
--- a/modules/juce_core/native/juce_curl_Network.cpp | |||
+++ b/modules/juce_core/native/juce_curl_Network.cpp | |||
@@ -338,6 +338,9 @@ public: | |||
// or 3) data is in the in buffer | |||
while ((! finished) && curlBuffer.getSize() == 0) | |||
{ | |||
+ if (Thread::currentThreadShouldExit()) | |||
+ return false; | |||
+ | |||
{ | |||
const ScopedLock lock (cleanupLock); | |||
diff --git a/modules/juce_dsp/native/juce_sse_SIMDNativeOps.h b/modules/juce_dsp/native/juce_sse_SIMDNativeOps.h | |||
index 7f64d433d..e9360e0de 100644 | |||
--- a/modules/juce_dsp/native/juce_sse_SIMDNativeOps.h | |||
+++ b/modules/juce_dsp/native/juce_sse_SIMDNativeOps.h | |||
@@ -74,6 +74,7 @@ struct SIMDNativeOps<float> | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE add (__m128 a, __m128 b) noexcept { return _mm_add_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE sub (__m128 a, __m128 b) noexcept { return _mm_sub_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE mul (__m128 a, __m128 b) noexcept { return _mm_mul_ps (a, b); } | |||
+ static forcedinline __m128 JUCE_VECTOR_CALLTYPE div (__m128 a, __m128 b) noexcept { return _mm_div_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_and (__m128 a, __m128 b) noexcept { return _mm_and_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_or (__m128 a, __m128 b) noexcept { return _mm_or_ps (a, b); } | |||
static forcedinline __m128 JUCE_VECTOR_CALLTYPE bit_xor (__m128 a, __m128 b) noexcept { return _mm_xor_ps (a, b); } | |||
@@ -142,6 +143,7 @@ struct SIMDNativeOps<double> | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE add (__m128d a, __m128d b) noexcept { return _mm_add_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE sub (__m128d a, __m128d b) noexcept { return _mm_sub_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE mul (__m128d a, __m128d b) noexcept { return _mm_mul_pd (a, b); } | |||
+ static forcedinline __m128d JUCE_VECTOR_CALLTYPE div (__m128d a, __m128d b) noexcept { return _mm_div_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_and (__m128d a, __m128d b) noexcept { return _mm_and_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_or (__m128d a, __m128d b) noexcept { return _mm_or_pd (a, b); } | |||
static forcedinline __m128d JUCE_VECTOR_CALLTYPE bit_xor (__m128d a, __m128d b) noexcept { return _mm_xor_pd (a, b); } | |||
diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm | |||
index 7a0718868..9d83519ff 100644 | |||
--- a/modules/juce_events/native/juce_mac_MessageManager.mm | |||
+++ b/modules/juce_events/native/juce_mac_MessageManager.mm | |||
@@ -369,6 +369,7 @@ void MessageManager::runDispatchLoop() | |||
static void shutdownNSApp() | |||
{ | |||
[NSApp stop: nil]; | |||
+ [NSEvent stopPeriodicEvents]; | |||
[NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; | |||
} | |||
diff --git a/modules/juce_gui_basics/buttons/juce_Button.cpp b/modules/juce_gui_basics/buttons/juce_Button.cpp | |||
index 1df2d6e20..e5c243eda 100644 | |||
--- a/modules/juce_gui_basics/buttons/juce_Button.cpp | |||
+++ b/modules/juce_gui_basics/buttons/juce_Button.cpp | |||
@@ -482,10 +482,7 @@ void Button::mouseDrag (const MouseEvent& e) | |||
bool Button::isMouseSourceOver (const MouseEvent& e) | |||
{ | |||
- if (e.source.isTouch() || e.source.isPen()) | |||
- return getLocalBounds().toFloat().contains (e.position); | |||
- | |||
- return isMouseOver(); | |||
+ return getLocalBounds().toFloat().contains (e.position); | |||
} | |||
void Button::focusGained (FocusChangeType) | |||
diff --git a/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp b/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp | |||
index 314544638..f3b61e2c3 100644 | |||
--- a/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp | |||
+++ b/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp | |||
@@ -76,9 +76,6 @@ void ShapeButton::setShape (const Path& newShape, | |||
shape = newShape; | |||
maintainShapeProportions = maintainShapeProportions_; | |||
- shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int>())); | |||
- setComponentEffect (hasShadow ? &shadow : nullptr); | |||
- | |||
if (resizeNowToFitThisShape) | |||
{ | |||
auto newBounds = shape.getBounds(); | |||
diff --git a/modules/juce_gui_basics/buttons/juce_ShapeButton.h b/modules/juce_gui_basics/buttons/juce_ShapeButton.h | |||
index 6863b1cab..5f9cee9ee 100644 | |||
--- a/modules/juce_gui_basics/buttons/juce_ShapeButton.h | |||
+++ b/modules/juce_gui_basics/buttons/juce_ShapeButton.h | |||
@@ -88,6 +88,8 @@ public: | |||
Colour overColourOn, | |||
Colour downColourOn); | |||
+ void setShadowColour (Colour shadow) { shadowColour = shadow; } | |||
+ | |||
/** Set whether the button should use the 'on' set of colours when its toggle state is 'on'. | |||
By default these will be the same as the normal colours but the setOnColours method can be | |||
used to provide a different set of colours. | |||
@@ -112,9 +114,8 @@ public: | |||
private: | |||
//============================================================================== | |||
Colour normalColour, overColour, downColour, | |||
- normalColourOn, overColourOn, downColourOn, outlineColour; | |||
+ normalColourOn, overColourOn, downColourOn, outlineColour, shadowColour; | |||
bool useOnColours; | |||
- DropShadowEffect shadow; | |||
Path shape; | |||
BorderSize<int> border; | |||
bool maintainShapeProportions; | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp | |||
index fa8d3ccf5..b2d75c655 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.cpp | |||
+++ b/modules/juce_gui_basics/components/juce_Component.cpp | |||
@@ -3021,7 +3021,8 @@ void Component::modifierKeysChanged (const ModifierKeys& modifiers) | |||
void Component::internalModifierKeysChanged() | |||
{ | |||
- sendFakeMouseMove(); | |||
+ auto mainMouse = Desktop::getInstance().getMainMouseSource(); | |||
+ mainMouse.triggerFakeMove(); | |||
modifierKeysChanged (ModifierKeys::currentModifiers); | |||
} | |||
diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h | |||
index 3add1db6b..ca84bc80e 100644 | |||
--- a/modules/juce_gui_basics/components/juce_Component.h | |||
+++ b/modules/juce_gui_basics/components/juce_Component.h | |||
@@ -439,7 +439,7 @@ public: | |||
@see setBounds, ComponentListener::componentMovedOrResized | |||
*/ | |||
- void setTopLeftPosition (Point<int> newTopLeftPosition); | |||
+ virtual void setTopLeftPosition (Point<int> newTopLeftPosition); | |||
/** Moves the component to a new position. | |||
diff --git a/modules/juce_gui_basics/keyboard/juce_CaretComponent.cpp b/modules/juce_gui_basics/keyboard/juce_CaretComponent.cpp | |||
index 7c6b83fbf..a84cf5a91 100644 | |||
--- a/modules/juce_gui_basics/keyboard/juce_CaretComponent.cpp | |||
+++ b/modules/juce_gui_basics/keyboard/juce_CaretComponent.cpp | |||
@@ -43,14 +43,9 @@ void CaretComponent::paint (Graphics& g) | |||
g.fillRect (getLocalBounds()); | |||
} | |||
-void CaretComponent::timerCallback() | |||
-{ | |||
- setVisible (shouldBeShown() && ! isVisible()); | |||
-} | |||
void CaretComponent::setCaretPosition (const Rectangle<int>& characterArea) | |||
{ | |||
- startTimer (380); | |||
setVisible (shouldBeShown()); | |||
setBounds (characterArea.withWidth (2)); | |||
} | |||
diff --git a/modules/juce_gui_basics/keyboard/juce_CaretComponent.h b/modules/juce_gui_basics/keyboard/juce_CaretComponent.h | |||
index a10b26c69..97894c075 100644 | |||
--- a/modules/juce_gui_basics/keyboard/juce_CaretComponent.h | |||
+++ b/modules/juce_gui_basics/keyboard/juce_CaretComponent.h | |||
@@ -31,8 +31,7 @@ namespace juce | |||
@tags{GUI} | |||
*/ | |||
-class JUCE_API CaretComponent : public Component, | |||
- private Timer | |||
+class JUCE_API CaretComponent : public Component | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -73,7 +72,6 @@ private: | |||
Component* owner; | |||
bool shouldBeShown() const; | |||
- void timerCallback() override; | |||
JUCE_DECLARE_NON_COPYABLE (CaretComponent) | |||
}; | |||
diff --git a/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp b/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp | |||
index 9ea63b215..7e8f14993 100644 | |||
--- a/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp | |||
+++ b/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp | |||
@@ -271,23 +271,6 @@ void ComponentBoundsConstrainer::checkBounds (Rectangle<int>& bounds, | |||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio)); | |||
} | |||
} | |||
- | |||
- if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) | |||
- { | |||
- bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2); | |||
- } | |||
- else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) | |||
- { | |||
- bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2); | |||
- } | |||
- else | |||
- { | |||
- if (isStretchingLeft) | |||
- bounds.setX (old.getRight() - bounds.getWidth()); | |||
- | |||
- if (isStretchingTop) | |||
- bounds.setY (old.getBottom() - bounds.getHeight()); | |||
- } | |||
} | |||
jassert (! bounds.isEmpty()); | |||
diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.cpp | |||
index 738fed8c1..f9f79994c 100644 | |||
--- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.cpp | |||
+++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.cpp | |||
@@ -41,8 +41,6 @@ LookAndFeel_V1::LookAndFeel_V1() | |||
setColour (PopupMenu::highlightedBackgroundColourId, Colour (0xbfa4c2ce)); | |||
setColour (PopupMenu::highlightedTextColourId, Colours::black); | |||
setColour (TextEditor::focusedOutlineColourId, findColour (TextButton::buttonColourId)); | |||
- | |||
- scrollbarShadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 2, Point<int>())); | |||
} | |||
LookAndFeel_V1::~LookAndFeel_V1() | |||
@@ -291,7 +289,7 @@ void LookAndFeel_V1::drawScrollbar (Graphics& g, ScrollBar& bar, | |||
ImageEffectFilter* LookAndFeel_V1::getScrollbarEffect() | |||
{ | |||
- return &scrollbarShadow; | |||
+ return nullptr; | |||
} | |||
@@ -477,7 +475,7 @@ Button* LookAndFeel_V1::createSliderButton (Slider&, const bool isIncrement) | |||
ImageEffectFilter* LookAndFeel_V1::getSliderEffect (Slider&) | |||
{ | |||
- return &scrollbarShadow; | |||
+ return nullptr; | |||
} | |||
int LookAndFeel_V1::getSliderThumbRadius (Slider&) | |||
diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h | |||
index b6c05aed7..51508a57a 100644 | |||
--- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h | |||
+++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h | |||
@@ -97,8 +97,6 @@ public: | |||
bool positionTitleBarButtonsOnLeft) override; | |||
private: | |||
- DropShadowEffect scrollbarShadow; | |||
- | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeel_V1) | |||
}; | |||
diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V4.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V4.cpp | |||
index 21042d0b2..829fed8e6 100644 | |||
--- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V4.cpp | |||
+++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V4.cpp | |||
@@ -1292,8 +1292,6 @@ void LookAndFeel_V4::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, | |||
{ | |||
cachedImage = { Image::ARGB, box.getWidth(), box.getHeight(), true }; | |||
Graphics g2 (cachedImage); | |||
- | |||
- DropShadow (Colours::black.withAlpha (0.7f), 8, { 0, 2 }).drawForPath (g2, path); | |||
} | |||
g.setColour (Colours::black); | |||
diff --git a/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp b/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp | |||
index 7c0848644..2017bccb5 100644 | |||
--- a/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp | |||
+++ b/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp | |||
@@ -30,9 +30,6 @@ BubbleComponent::BubbleComponent() | |||
: allowablePlacements (above | below | left | right) | |||
{ | |||
setInterceptsMouseClicks (false, false); | |||
- | |||
- shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.35f), 5, Point<int>())); | |||
- setComponentEffect (&shadow); | |||
} | |||
BubbleComponent::~BubbleComponent() {} | |||
diff --git a/modules/juce_gui_basics/misc/juce_BubbleComponent.h b/modules/juce_gui_basics/misc/juce_BubbleComponent.h | |||
index f086c8ff3..cca6205a3 100644 | |||
--- a/modules/juce_gui_basics/misc/juce_BubbleComponent.h | |||
+++ b/modules/juce_gui_basics/misc/juce_BubbleComponent.h | |||
@@ -178,7 +178,6 @@ private: | |||
Rectangle<int> content; | |||
Point<int> arrowTip; | |||
int allowablePlacements; | |||
- DropShadowEffect shadow; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BubbleComponent) | |||
}; | |||
diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp | |||
index f18b890f4..e92e9af14 100644 | |||
--- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp | |||
@@ -1816,7 +1816,7 @@ public: | |||
if (peerIsDeleted) | |||
return S_FALSE; | |||
- peer.handleDragExit (dragInfo); | |||
+ peer.handleDragDrop (dragInfo); | |||
return S_OK; | |||
} | |||
diff --git a/modules/juce_gui_basics/widgets/juce_Slider.cpp b/modules/juce_gui_basics/widgets/juce_Slider.cpp | |||
index 731193ff1..0f68f6efa 100644 | |||
--- a/modules/juce_gui_basics/widgets/juce_Slider.cpp | |||
+++ b/modules/juce_gui_basics/widgets/juce_Slider.cpp | |||
@@ -749,7 +749,7 @@ public: | |||
? e.position.x - mouseDragStartPos.x | |||
: mouseDragStartPos.y - e.position.y; | |||
- newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
+ newPos = owner.valueToProportionOfLength (valueWhenLastDragged) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
if (style == IncDecButtons) | |||
@@ -763,7 +763,7 @@ public: | |||
auto mouseDiff = (e.position.x - mouseDragStartPos.x) | |||
+ (mouseDragStartPos.y - e.position.y); | |||
- newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
+ newPos = owner.valueToProportionOfLength (valueWhenLastDragged) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
} | |||
else | |||
@@ -774,6 +774,7 @@ public: | |||
newPos = 1.0 - newPos; | |||
} | |||
+ mouseDragStartPos = e.position; | |||
newPos = (isRotary() && ! rotaryParams.stopAtEnd) ? newPos - std::floor (newPos) | |||
: jlimit (0.0, 1.0, newPos); | |||
valueWhenLastDragged = owner.proportionOfLengthToValue (newPos); | |||
diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp | |||
index 233c23305..70afc33a1 100644 | |||
--- a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp | |||
+++ b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp | |||
@@ -833,6 +833,11 @@ struct TextEditor::TextHolderComponent : public Component, | |||
{ | |||
owner.drawContent (g); | |||
} | |||
+ | |||
+ void setTopLeftPosition(Point<int> new_position) override { | |||
+ Component::setTopLeftPosition(new_position); | |||
+ owner.textChanged(); | |||
+ } | |||
void restartTimer() | |||
{ | |||
@@ -1558,6 +1563,9 @@ void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting) | |||
moveCaret (newPosition); | |||
selection = Range<int>::emptyRange (getCaretPosition()); | |||
} | |||
+ | |||
+ if (listeners.size() != 0 || onTextChange != nullptr) | |||
+ postCommandMessage (TextEditorDefs::textChangeMessageId); | |||
} | |||
int TextEditor::getTextIndexAt (const int x, const int y) | |||
@@ -2141,6 +2149,9 @@ void TextEditor::focusGained (FocusChangeType cause) | |||
repaint(); | |||
updateCaretPosition(); | |||
+ | |||
+ if (listeners.size() != 0 || onTextChange != nullptr) | |||
+ postCommandMessage (TextEditorDefs::textChangeMessageId); | |||
} | |||
void TextEditor::focusLost (FocusChangeType) |
@@ -0,0 +1,18 @@ | |||
diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp | |||
index 206a64afc..aa6196934 100644 | |||
--- a/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp | |||
+++ b/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp | |||
@@ -67,11 +67,11 @@ namespace FUnknownPrivate { | |||
//------------------------------------------------------------------------ | |||
int32 PLUGIN_API atomicAdd (int32& var, int32 d) | |||
{ | |||
-#if SMTG_OS_WINDOWS | |||
+#if SMTG_OS_WINDOWS && !defined(__MINGW32__) | |||
return InterlockedExchangeAdd (&var, d) + d; | |||
#elif SMTG_OS_MACOS | |||
return OSAtomicAdd32Barrier (d, (int32_t*)&var); | |||
-#elif SMTG_OS_LINUX | |||
+#elif SMTG_OS_LINUX || defined(__MINGW32__) | |||
__gnu_cxx::__atomic_add (&var, d); | |||
return var; | |||
#else |
@@ -0,0 +1,98 @@ | |||
 | |||
JUCE is an open-source cross-platform C++ application framework used for rapidly | |||
developing high quality desktop and mobile applications, including VST, AU (and AUv3), | |||
RTAS and AAX audio plug-ins. JUCE can be easily integrated with existing projects or can | |||
be used as a project generation tool via the [Projucer](https://juce.com/discover/projucer), | |||
which supports exporting projects for Xcode (macOS and iOS), Visual Studio, Android Studio, | |||
Code::Blocks, CLion and Linux Makefiles as well as containing a source code editor and | |||
live-coding engine which can be used for rapid prototyping. | |||
## Getting Started | |||
The JUCE repository contains a [master](https://github.com/juce-framework/JUCE/tree/master) | |||
and [develop](https://github.com/juce-framework/JUCE/tree/develop) branch. The develop branch | |||
contains the latest bugfixes and features and is periodically merged into the master | |||
branch in stable [tagged releases](https://github.com/juce-framework/JUCE/releases) | |||
(the latest release containing pre-built binaries can be also downloaded from the | |||
[JUCE website](https://juce.com/get-juce)). | |||
JUCE projects can be managed with either the Projucer (JUCE's own project-configuration | |||
tool) or with CMake. | |||
### The Projucer | |||
The repository doesn't contain a pre-built Projucer so you will need to build it | |||
for your platform - Xcode, Visual Studio and Linux Makefile projects are located in | |||
[extras/Projucer/Builds](/extras/Projucer/Builds) | |||
(the minumum system requirements are listed in the __System Requirements__ section below). | |||
The Projucer can then be used to create new JUCE projects, view tutorials and run examples. | |||
It is also possible to include the JUCE modules source code in an existing project directly, | |||
or build them into a static or dynamic library which can be linked into a project. | |||
For further help getting started, please refer to the JUCE | |||
[documentation](https://juce.com/learn/documentation) and | |||
[tutorials](https://juce.com/learn/tutorials). | |||
### CMake | |||
Version 3.15 or higher is required for plugin projects, and strongly | |||
recommended for other project types. To use CMake, you will need to install it, | |||
either from your system package manager or from the [official download | |||
page](https://cmake.org/download/). For comprehensive documentation on JUCE's | |||
CMake API, see the [JUCE CMake documentation](/docs/CMake%20API.md). For examples | |||
which may be useful as starting points for new CMake projects, see the [CMake | |||
examples directory](/examples/CMake). | |||
#### Building Examples | |||
To use CMake to build the examples and extras bundled with JUCE, simply clone | |||
JUCE and then run the following commands, replacing "DemoRunner" with the name | |||
of the target you wish to build. | |||
cd /path/to/JUCE | |||
cmake . -B cmake-build -DJUCE_BUILD_EXAMPLES=ON -DJUCE_BUILD_EXTRAS=ON | |||
cmake --build cmake-build --target DemoRunner | |||
## Minimum System Requirements | |||
#### Building JUCE Projects | |||
- __macOS/iOS__: macOS 10.11 and Xcode 7.3.1 | |||
- __Windows__: Windows 8.1 and Visual Studio 2015 64-bit | |||
- __Linux__: GCC 4.8 (for a full list of dependencies, see | |||
[here](/docs/Linux%20Dependencies.md)). | |||
- __Android__: Android Studio on Windows, macOS or Linux | |||
#### Deployment Targets | |||
- __macOS__: macOS 10.7 | |||
- __Windows__: Windows Vista | |||
- __Linux__: Mainstream Linux distributions | |||
- __iOS__: iOS 9.0 | |||
- __Android__: Jelly Bean (API 16) | |||
## Contributing | |||
For bug reports and features requests, please visit the [JUCE Forum](https://forum.juce.com/) - | |||
the JUCE developers are active there and will read every post and respond accordingly. When | |||
submitting a bug report, please ensure that it follows the | |||
[issue template](/.github/ISSUE_TEMPLATE.txt). | |||
We don't accept third party GitHub pull requests directly due to copyright restrictions | |||
but if you would like to contribute any changes please contact us. | |||
## License | |||
The core JUCE modules (juce_audio_basics, juce_audio_devices, juce_blocks_basics, juce_core | |||
and juce_events) are permissively licensed under the terms of the | |||
[ISC license](http://www.isc.org/downloads/software-support-policy/isc-license/). | |||
Other modules are covered by a | |||
[GPL/Commercial license](https://www.gnu.org/licenses/gpl-3.0.en.html). | |||
There are multiple commercial licensing tiers for JUCE, with different terms for each: | |||
- JUCE Personal (developers or startup businesses with revenue under 50K USD) - free | |||
- JUCE Indie (small businesses with revenue under 500K USD) - $40/month | |||
- JUCE Pro (no revenue limit) - $130/month | |||
- JUCE Educational (no revenue limit) - free for bona fide educational institutes | |||
For full terms see [LICENSE.md](LICENSE.md). |
@@ -0,0 +1,182 @@ | |||
/* | |||
============================================================================== | |||
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 | |||
{ | |||
return timeInSamples == other.timeInSamples | |||
&& ppqPosition == other.ppqPosition | |||
&& editOriginTime == other.editOriginTime | |||
&& ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart | |||
&& frameRate == other.frameRate | |||
&& isPlaying == other.isPlaying | |||
&& isRecording == other.isRecording | |||
&& bpm == other.bpm | |||
&& timeSigNumerator == other.timeSigNumerator | |||
&& timeSigDenominator == other.timeSigDenominator | |||
&& ppqLoopStart == other.ppqLoopStart | |||
&& ppqLoopEnd == other.ppqLoopEnd | |||
&& isLooping == other.isLooping; | |||
} | |||
bool operator!= (const CurrentPositionInfo& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
void resetToDefault() | |||
{ | |||
zerostruct (*this); | |||
timeSigNumerator = 4; | |||
timeSigDenominator = 4; | |||
bpm = 120; | |||
} | |||
}; | |||
//============================================================================== | |||
/** 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,725 @@ | |||
/* | |||
============================================================================== | |||
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::create7point0point4() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topFrontLeft) | (1u << topFrontRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
AudioChannelSet AudioChannelSet::create7point1point4() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topFrontLeft) | (1u << topFrontRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
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,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 | |||
{ | |||
//============================================================================== | |||
/** | |||
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 Dolby Atmos 7.0.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: n/a (VST), n/a (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point0point4(); | |||
/** Creates a set for Dolby Atmos 7.1.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: k71_4 (VST), n/a (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point4(); | |||
//============================================================================== | |||
/** 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) | |||
{ | |||
*unalignedPointerCast<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; | |||
*unalignedPointerCast<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) | |||
{ | |||
*unalignedPointerCast<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; | |||
*unalignedPointerCast<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) | |||
{ | |||
*unalignedPointerCast<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; | |||
*unalignedPointerCast<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) | |||
{ | |||
*unalignedPointerCast<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; | |||
*unalignedPointerCast<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) | |||
{ | |||
*unalignedPointerCast<float*> (d) = source[i]; | |||
#if JUCE_BIG_ENDIAN | |||
*unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<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) | |||
{ | |||
*unalignedPointerCast<float*> (d) = source[i]; | |||
#if JUCE_LITTLE_ENDIAN | |||
*unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<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 (*unalignedPointerCast<const uint16*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast<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 (*unalignedPointerCast<const uint16*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<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 (*unalignedPointerCast<const uint32*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<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 (*unalignedPointerCast<const uint32*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<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] = *unalignedPointerCast<const float*> (s); | |||
#if JUCE_BIG_ENDIAN | |||
auto d = unalignedPointerCast<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] = *unalignedPointerCast<const float*> (s); | |||
#if JUCE_LITTLE_ENDIAN | |||
auto d = unalignedPointerCast<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.8 | |||
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,308 @@ | |||
/* | |||
============================================================================== | |||
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; | |||
const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1); | |||
return jmin (maxBytes, var.value + 2 + var.bytesUsed); | |||
} | |||
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 - start)); | |||
} | |||
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; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiBufferTest : public UnitTest | |||
{ | |||
MidiBufferTest() | |||
: UnitTest ("MidiBuffer", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("Clear messages"); | |||
{ | |||
const auto message = MidiMessage::noteOn (1, 64, 0.5f); | |||
const auto testBuffer = [&] | |||
{ | |||
MidiBuffer buffer; | |||
buffer.addEvent (message, 0); | |||
buffer.addEvent (message, 10); | |||
buffer.addEvent (message, 20); | |||
buffer.addEvent (message, 30); | |||
return buffer; | |||
}(); | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 0); | |||
expectEquals (buffer.getNumEvents(), 4); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 1); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 10); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 20); | |||
expectEquals (buffer.getNumEvents(), 2); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 30); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 300); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
} | |||
} | |||
}; | |||
static MidiBufferTest midiBufferTest; | |||
#endif | |||
} // 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,798 @@ | |||
/* | |||
============================================================================== | |||
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; | |||
} | |||
} | |||
template <typename Value> | |||
struct Optional | |||
{ | |||
Optional() = default; | |||
Optional (const Value& v) | |||
: value (v), valid (true) {} | |||
Value value = Value(); | |||
bool valid = false; | |||
}; | |||
template <typename Integral> | |||
struct ReadTrait; | |||
template <> | |||
struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; }; | |||
template <> | |||
struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; }; | |||
template <typename Integral> | |||
Optional<Integral> tryRead (const uint8*& data, size_t& remaining) | |||
{ | |||
using Trait = ReadTrait<Integral>; | |||
constexpr auto size = sizeof (Integral); | |||
if (remaining < size) | |||
return {}; | |||
const Optional<Integral> result { Trait::read (data) }; | |||
data += size; | |||
remaining -= size; | |||
return result; | |||
} | |||
struct HeaderDetails | |||
{ | |||
size_t bytesRead = 0; | |||
short timeFormat = 0; | |||
short fileType = 0; | |||
short numberOfTracks = 0; | |||
}; | |||
static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData, | |||
const size_t maxSize) | |||
{ | |||
auto* data = initialData; | |||
auto remaining = maxSize; | |||
auto ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
return {}; | |||
if (ch.value != ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
auto ok = false; | |||
if (ch.value == ByteOrder::bigEndianInt ("RIFF")) | |||
{ | |||
for (int i = 0; i < 8; ++i) | |||
{ | |||
ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
return {}; | |||
if (ch.value == ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
ok = true; | |||
break; | |||
} | |||
} | |||
} | |||
if (! ok) | |||
return {}; | |||
} | |||
const auto bytesRemaining = tryRead<uint32> (data, remaining); | |||
if (! bytesRemaining.valid || bytesRemaining.value > remaining) | |||
return {}; | |||
const auto optFileType = tryRead<uint16> (data, remaining); | |||
if (! optFileType.valid || 2 < optFileType.value) | |||
return {}; | |||
const auto optNumTracks = tryRead<uint16> (data, remaining); | |||
if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1)) | |||
return {}; | |||
const auto optTimeFormat = tryRead<uint16> (data, remaining); | |||
if (! optTimeFormat.valid) | |||
return {}; | |||
HeaderDetails result; | |||
result.fileType = (short) optFileType.value; | |||
result.timeFormat = (short) optTimeFormat.value; | |||
result.numberOfTracks = (short) optNumTracks.value; | |||
result.bytesRead = maxSize - remaining; | |||
return { result }; | |||
} | |||
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); | |||
} | |||
} | |||
} | |||
static MidiMessageSequence readTrack (const uint8* data, int size) | |||
{ | |||
double time = 0; | |||
uint8 lastStatusByte = 0; | |||
MidiMessageSequence result; | |||
while (size > 0) | |||
{ | |||
const auto delay = MidiMessage::readVariableLengthValue (data, (int) size); | |||
if (! delay.isValid()) | |||
break; | |||
data += delay.bytesUsed; | |||
size -= delay.bytesUsed; | |||
time += delay.value; | |||
if (size <= 0) | |||
break; | |||
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; | |||
} | |||
return result; | |||
} | |||
} | |||
//============================================================================== | |||
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)) | |||
return false; | |||
auto size = data.getSize(); | |||
auto d = static_cast<const uint8*> (data.getData()); | |||
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); | |||
if (! optHeader.valid) | |||
return false; | |||
const auto header = optHeader.value; | |||
timeFormat = header.timeFormat; | |||
d += header.bytesRead; | |||
size -= (size_t) header.bytesRead; | |||
for (int track = 0; track < header.numberOfTracks; ++track) | |||
{ | |||
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkType.valid) | |||
return false; | |||
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkSize.valid) | |||
return false; | |||
const auto chunkSize = optChunkSize.value; | |||
if (size < chunkSize) | |||
return false; | |||
if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk")) | |||
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); | |||
size -= chunkSize; | |||
d += chunkSize; | |||
} | |||
return size == 0; | |||
} | |||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||
{ | |||
auto sequence = MidiFileHelpers::readTrack (data, size); | |||
// sort so that we put all the note-offs before note-ons that have the same time | |||
std::stable_sort (sequence.list.begin(), sequence.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(); | |||
}); | |||
if (createMatchingNoteOffs) | |||
sequence.updateMatchedPairs(); | |||
addTrack (sequence); | |||
} | |||
//============================================================================== | |||
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; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiFileTest : public UnitTest | |||
{ | |||
MidiFileTest() | |||
: UnitTest ("MidiFile", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("ReadTrack respects running status"); | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
MidiFileHelpers::writeVariableLengthInt (os, 100); | |||
writeBytes (os, { 0x90, 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 200); | |||
writeBytes (os, { 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 300); | |||
writeBytes (os, { 0xff, 0x2f, 0x00 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 3); | |||
expect (sequence.getEventPointer (0)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (1)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent()); | |||
} | |||
beginTest ("ReadTrack returns available messages if input is truncated"); | |||
{ | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Incomplete delta time | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with no following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with malformed following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
writeBytes (os, { 0x90, 0x40 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 1); | |||
expect (sequence.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40); | |||
expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00); | |||
} | |||
} | |||
beginTest ("Header parsing works"); | |||
{ | |||
{ | |||
// No data | |||
const auto header = parseHeader ([] (OutputStream&) {}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Invalid initial byte | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Type block, but no header data | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// We (ll-formed header, but track type is 0 and channels != 1 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Well-formed header, but track type is 5 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Well-formed header | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); | |||
}); | |||
expect (header.valid); | |||
expectEquals (header.value.fileType, (short) 1); | |||
expectEquals (header.value.numberOfTracks, (short) 16); | |||
expectEquals (header.value.timeFormat, (short) 1); | |||
expectEquals ((int) header.value.bytesRead, 14); | |||
} | |||
} | |||
beginTest ("Read from stream"); | |||
{ | |||
{ | |||
// Empty input | |||
const auto file = parseFile ([] (OutputStream&) {}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Malformed header | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, no channels | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 0); | |||
} | |||
{ | |||
// Header, one malformed channel | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', '?' }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, one channel with malformed message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
expectEquals (file.value.getTrack (0)->getNumEvents(), 0); | |||
} | |||
{ | |||
// Header, one channel with incorrect length message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, one channel, all well-formed | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 0x0f); | |||
writeBytes (os, { 0x80, 0x00, 0x00 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
auto& track = *file.value.getTrack (0); | |||
expectEquals (track.getNumEvents(), 1); | |||
expect (track.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); | |||
} | |||
} | |||
} | |||
template <typename Fn> | |||
static MidiMessageSequence parseSequence (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()), | |||
(int) os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()), | |||
os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFile> parseFile (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
MemoryInputStream is (os.getData(), os.getDataSize(), false); | |||
MidiFile mf; | |||
if (mf.readFrom (is)) | |||
return mf; | |||
return {}; | |||
} | |||
static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes) | |||
{ | |||
for (const auto& byte : bytes) | |||
os.writeByte ((char) byte); | |||
} | |||
}; | |||
static MidiFileTest midiFileTests; | |||
#endif | |||
} // 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 JUCE_API 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,986 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
This signature has been deprecated in favour of the safer | |||
readVariableLengthValue. | |||
The `data` argument indicates the data to read the number from, | |||
and `numBytesUsed` is used as an out-parameter to indicate the number | |||
of bytes that were read. | |||
*/ | |||
JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data, | |||
int& numBytesUsed) noexcept); | |||
/** Holds information about a variable-length value which was parsed | |||
from a stream of bytes. | |||
A valid value requires that `bytesUsed` is greater than 0. | |||
*/ | |||
struct VariableLengthValue | |||
{ | |||
VariableLengthValue() = default; | |||
VariableLengthValue (int valueIn, int bytesUsedIn) | |||
: value (valueIn), bytesUsed (bytesUsedIn) {} | |||
bool isValid() const noexcept { return bytesUsed > 0; } | |||
int value = 0; | |||
int bytesUsed = 0; | |||
}; | |||
/** Reads a midi variable-length integer, with protection against buffer overflow. | |||
@param data the data to read the number from | |||
@param maxBytesToUse the number of bytes in the region following `data` | |||
@returns a struct containing the parsed value, and the number | |||
of bytes that were read. If parsing fails, both the | |||
`value` and `bytesUsed` fields will be set to 0 and | |||
`isValid()` will return false | |||
*/ | |||
static VariableLengthValue readVariableLengthValue (const uint8* data, | |||
int maxBytesToUse) 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,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 > 0 && 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,415 @@ | |||
/* | |||
============================================================================== | |||
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 resetLastReceivedValues(); | |||
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 <= 16) | |||
{ | |||
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,332 @@ | |||
/* | |||
============================================================================== | |||
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 } }, | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
{ 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 } }, | |||
#endif | |||
{ 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,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. | |||
============================================================================== | |||
*/ | |||
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) | |||
{ | |||
if (buffer.getNumSamples() == 0) | |||
{ | |||
bufferToFill.clearActiveBufferRegion(); | |||
return; | |||
} | |||
auto& dst = *bufferToFill.buffer; | |||
auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels()); | |||
int max = 0, pos = 0; | |||
auto n = buffer.getNumSamples(); | |||
auto m = bufferToFill.numSamples; | |||
int i = position; | |||
for (; (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; | |||
} | |||
//============================================================================== | |||
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; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
static bool operator== (const AudioBuffer<float>& a, const AudioBuffer<float>& b) | |||
{ | |||
if (a.getNumChannels() != b.getNumChannels()) | |||
return false; | |||
for (int channel = 0; channel < a.getNumChannels(); ++channel) | |||
{ | |||
auto* aPtr = a.getReadPointer (channel); | |||
auto* bPtr = b.getReadPointer (channel); | |||
if (std::vector<float> (aPtr, aPtr + a.getNumSamples()) | |||
!= std::vector<float> (bPtr, bPtr + b.getNumSamples())) | |||
{ | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
struct MemoryAudioSourceTests : public UnitTest | |||
{ | |||
MemoryAudioSourceTests() : UnitTest ("MemoryAudioSource", UnitTestCategories::audio) {} | |||
void runTest() override | |||
{ | |||
constexpr int blockSize = 512; | |||
AudioBuffer<float> bufferToFill { 2, blockSize }; | |||
AudioSourceChannelInfo channelInfo { bufferToFill }; | |||
beginTest ("A zero-length buffer produces silence, whether or not looping is enabled"); | |||
{ | |||
for (const bool enableLooping : { false, true }) | |||
{ | |||
AudioBuffer<float> buffer; | |||
MemoryAudioSource source { buffer, true, false }; | |||
source.setLooping (enableLooping); | |||
source.prepareToPlay (blockSize, 44100.0); | |||
for (int i = 0; i < 2; ++i) | |||
{ | |||
play (source, channelInfo); | |||
expect (isSilent (bufferToFill)); | |||
} | |||
} | |||
} | |||
beginTest ("A short buffer without looping is played once and followed by silence"); | |||
{ | |||
auto buffer = getShortBuffer(); | |||
MemoryAudioSource source { buffer, true, false }; | |||
source.setLooping (false); | |||
source.prepareToPlay (blockSize, 44100.0); | |||
play (source, channelInfo); | |||
auto copy = buffer; | |||
copy.setSize (buffer.getNumChannels(), blockSize, true, true, false); | |||
expect (bufferToFill == copy); | |||
play (source, channelInfo); | |||
expect (isSilent (bufferToFill)); | |||
} | |||
beginTest ("A short buffer with looping is played multiple times"); | |||
{ | |||
auto buffer = getShortBuffer(); | |||
MemoryAudioSource source { buffer, true, false }; | |||
source.setLooping (true); | |||
source.prepareToPlay (blockSize, 44100.0); | |||
play (source, channelInfo); | |||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) | |||
expect (bufferToFill.getSample (0, sample + buffer.getNumSamples()) == buffer.getSample (0, sample)); | |||
expect (! isSilent (bufferToFill)); | |||
} | |||
beginTest ("A long buffer without looping is played once"); | |||
{ | |||
auto buffer = getLongBuffer(); | |||
MemoryAudioSource source { buffer, true, false }; | |||
source.setLooping (false); | |||
source.prepareToPlay (blockSize, 44100.0); | |||
play (source, channelInfo); | |||
auto copy = buffer; | |||
copy.setSize (buffer.getNumChannels(), blockSize, true, true, false); | |||
expect (bufferToFill == copy); | |||
for (int i = 0; i < 10; ++i) | |||
play (source, channelInfo); | |||
expect (isSilent (bufferToFill)); | |||
} | |||
beginTest ("A long buffer with looping is played multiple times"); | |||
{ | |||
auto buffer = getLongBuffer(); | |||
MemoryAudioSource source { buffer, true, false }; | |||
source.setLooping (true); | |||
source.prepareToPlay (blockSize, 44100.0); | |||
for (int i = 0; i < 100; ++i) | |||
{ | |||
play (source, channelInfo); | |||
expect (bufferToFill.getSample (0, 0) == buffer.getSample (0, (i * blockSize) % buffer.getNumSamples())); | |||
} | |||
} | |||
} | |||
static AudioBuffer<float> getTestBuffer (int length) | |||
{ | |||
AudioBuffer<float> buffer { 2, length }; | |||
for (int channel = 0; channel < buffer.getNumChannels(); ++channel) | |||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) | |||
buffer.setSample (channel, sample, jmap ((float) sample, 0.0f, (float) length, -1.0f, 1.0f)); | |||
return buffer; | |||
} | |||
static AudioBuffer<float> getShortBuffer() { return getTestBuffer (5); } | |||
static AudioBuffer<float> getLongBuffer() { return getTestBuffer (1000); } | |||
static void play (MemoryAudioSource& source, AudioSourceChannelInfo& info) | |||
{ | |||
info.clearActiveBufferRegion(); | |||
source.getNextAudioBlock (info); | |||
} | |||
static bool isSilent (const AudioBuffer<float>& b) | |||
{ | |||
for (int channel = 0; channel < b.getNumChannels(); ++channel) | |||
if (b.findMinMax (channel, 0, b.getNumSamples()) != Range<float>{}) | |||
return false; | |||
return true; | |||
} | |||
}; | |||
static MemoryAudioSourceTests memoryAudioSourceTests; | |||
#endif | |||
} // 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 |