Browse Source

Add up-to-date juce as juce-current

Signed-off-by: falkTX <falktx@falktx.com>
tags/2020-12-27
falkTX 3 years ago
parent
commit
365f5652b4
100 changed files with 43265 additions and 0 deletions
  1. +518
    -0
      libs/juce-current/AppConfig.h
  2. +49
    -0
      libs/juce-current/meson.build
  3. +157
    -0
      libs/juce-current/source/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h
  4. +723
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioChannelSet.cpp
  5. +484
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioChannelSet.h
  6. +596
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioDataConverters.cpp
  7. +716
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioDataConverters.h
  8. +75
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp
  9. +96
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h
  10. +1194
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
  11. +1305
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
  12. +257
    -0
      libs/juce-current/source/juce_audio_basics/buffers/juce_FloatVectorOperations.h
  13. +88
    -0
      libs/juce-current/source/juce_audio_basics/juce_audio_basics.cpp
  14. +122
    -0
      libs/juce-current/source/juce_audio_basics/juce_audio_basics.h
  15. +23
    -0
      libs/juce-current/source/juce_audio_basics/juce_audio_basics.mm
  16. +240
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiBuffer.cpp
  17. +346
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiBuffer.h
  18. +446
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiFile.cpp
  19. +197
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiFile.h
  20. +177
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
  21. +196
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiKeyboardState.h
  22. +1144
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.cpp
  23. +952
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h
  24. +952
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h.orig
  25. +11
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h.rej
  26. +412
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
  27. +306
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessageSequence.h
  28. +380
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiRPN.cpp
  29. +154
    -0
      libs/juce-current/source/juce_audio_basics/midi/juce_MidiRPN.h
  30. +2288
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEInstrument.cpp
  31. +413
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEInstrument.h
  32. +238
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEMessages.cpp
  33. +116
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEMessages.h
  34. +127
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPENote.cpp
  35. +184
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPENote.h
  36. +338
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiser.cpp
  37. +312
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiser.h
  38. +376
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp
  39. +215
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserBase.h
  40. +50
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp
  41. +191
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h
  42. +494
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEUtils.cpp
  43. +153
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEUtils.h
  44. +173
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEValue.cpp
  45. +97
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEValue.h
  46. +386
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp
  47. +225
    -0
      libs/juce-current/source/juce_audio_basics/mpe/juce_MPEZoneLayout.h
  48. +330
    -0
      libs/juce-current/source/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h
  49. +179
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_AudioSource.h
  50. +315
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_BufferingAudioSource.cpp
  51. +119
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_BufferingAudioSource.h
  52. +187
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp
  53. +141
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h
  54. +80
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp
  55. +68
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_IIRFilterAudioSource.h
  56. +100
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_MemoryAudioSource.cpp
  57. +82
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_MemoryAudioSource.h
  58. +158
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_MixerAudioSource.cpp
  59. +99
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_MixerAudioSource.h
  60. +76
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_PositionableAudioSource.h
  61. +265
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp
  62. +106
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ResamplingAudioSource.h
  63. +83
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ReverbAudioSource.cpp
  64. +74
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ReverbAudioSource.h
  65. +78
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp
  66. +71
    -0
      libs/juce-current/source/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h
  67. +585
    -0
      libs/juce-current/source/juce_audio_basics/synthesisers/juce_Synthesiser.cpp
  68. +645
    -0
      libs/juce-current/source/juce_audio_basics/synthesisers/juce_Synthesiser.h
  69. +248
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_ADSR.h
  70. +112
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_Decibels.h
  71. +500
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_GenericInterpolator.h
  72. +336
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_IIRFilter.cpp
  73. +217
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_IIRFilter.h
  74. +191
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_Interpolators.cpp
  75. +245
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_Interpolators.h
  76. +62
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp
  77. +313
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_Reverb.h
  78. +92
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_SmoothedValue.cpp
  79. +630
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_SmoothedValue.h
  80. +10033
    -0
      libs/juce-current/source/juce_audio_basics/utilities/juce_WindowedSincInterpolator.cpp
  81. +1170
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
  82. +552
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioDeviceManager.h
  83. +45
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODevice.cpp
  84. +325
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODevice.h
  85. +89
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp
  86. +184
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
  87. +59
    -0
      libs/juce-current/source/juce_audio_devices/audio_io/juce_SystemAudioVolume.h
  88. +261
    -0
      libs/juce-current/source/juce_audio_devices/juce_audio_devices.cpp
  89. +186
    -0
      libs/juce-current/source/juce_audio_devices/juce_audio_devices.h
  90. +23
    -0
      libs/juce-current/source/juce_audio_devices/juce_audio_devices.mm
  91. +153
    -0
      libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiDevices.cpp
  92. +378
    -0
      libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiDevices.h
  93. +158
    -0
      libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp
  94. +113
    -0
      libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiMessageCollector.h
  95. +1094
    -0
      libs/juce-current/source/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java
  96. +188
    -0
      libs/juce-current/source/juce_audio_devices/native/juce_MidiDataConcatenator.h
  97. +496
    -0
      libs/juce-current/source/juce_audio_devices/native/juce_android_Audio.cpp
  98. +131
    -0
      libs/juce-current/source/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h
  99. +706
    -0
      libs/juce-current/source/juce_audio_devices/native/juce_android_Midi.cpp
  100. +1442
    -0
      libs/juce-current/source/juce_audio_devices/native/juce_android_Oboe.cpp

+ 518
- 0
libs/juce-current/AppConfig.h View File

@@ -0,0 +1,518 @@
/*
==============================================================================
Build options for juce static library
==============================================================================
*/
#ifndef BUILD_JUCE_APPCONFIG_H_INCLUDED
#define BUILD_JUCE_APPCONFIG_H_INCLUDED
#define JUCE_MODULE_AVAILABLE_juce_audio_basics 1
#define JUCE_MODULE_AVAILABLE_juce_audio_devices 1
#define JUCE_MODULE_AVAILABLE_juce_audio_formats 1
#define JUCE_MODULE_AVAILABLE_juce_audio_plugin_client 1
#define JUCE_MODULE_AVAILABLE_juce_audio_processors 1
#define JUCE_MODULE_AVAILABLE_juce_audio_utils 1
#define JUCE_MODULE_AVAILABLE_juce_blocks_basics 0
#define JUCE_MODULE_AVAILABLE_juce_box2d 0
#define JUCE_MODULE_AVAILABLE_juce_core 1
#define JUCE_MODULE_AVAILABLE_juce_cryptography 1
#define JUCE_MODULE_AVAILABLE_juce_data_structures 1
#define JUCE_MODULE_AVAILABLE_juce_dsp 1
#define JUCE_MODULE_AVAILABLE_juce_events 1
#define JUCE_MODULE_AVAILABLE_juce_graphics 1
#define JUCE_MODULE_AVAILABLE_juce_gui_basics 1
#define JUCE_MODULE_AVAILABLE_juce_gui_extra 1
#define JUCE_MODULE_AVAILABLE_juce_opengl 1
#define JUCE_MODULE_AVAILABLE_juce_osc 0
#define JUCE_MODULE_AVAILABLE_juce_product_unlocking 0
#define JUCE_MODULE_AVAILABLE_juce_video 0
//=============================================================================
#define JUCE_STANDALONE_APPLICATION 0
#define JUCE_REPORT_APP_USAGE 0
#define JUCE_DISPLAY_SPLASH_SCREEN 0
#define JUCE_USE_DARK_SPLASH_SCREEN 0
//=============================================================================
// juce_audio_basics
//=============================================================================
// juce_audio_devices
/** Config: JUCE_ASIO
Enables ASIO audio devices (MS Windows only).
Turning this on means that you'll need to have the Steinberg ASIO SDK installed
on your Windows build machine.
See the comments in the ASIOAudioIODevice class's header file for more
info about this.
*/
#define JUCE_ASIO 0
/** Config: JUCE_WASAPI
Enables WASAPI audio devices (Windows Vista and above).
*/
#define JUCE_WASAPI 0
/** Config: JUCE_DIRECTSOUND
Enables DirectSound audio (MS Windows only).
*/
#define JUCE_DIRECTSOUND 0
/** Config: JUCE_ALSA
Enables ALSA audio devices (Linux only).
*/
#if LINUX
#define JUCE_ALSA 1
#define JUCE_ALSA_MIDI_NAME "JuceMidi"
#else
#define JUCE_ALSA 0
#endif
/** Config: JUCE_JACK
Enables JACK audio devices (Linux only).
*/
#define JUCE_JACK 0
/** Config: JUCE_USE_ANDROID_OPENSLES
Enables OpenSLES devices (Android only).
*/
#define JUCE_USE_ANDROID_OPENSLES 0
/** Config: JUCE_USE_CDREADER
Enables the AudioCDReader class (on supported platforms).
*/
#define JUCE_USE_CDREADER 0
/** Config: JUCE_USE_CDBURNER
Enables the AudioCDBurner class (on supported platforms).
*/
#define JUCE_USE_CDBURNER 0
//=============================================================================
// juce_audio_formats
/** Config: JUCE_USE_FLAC
Enables the FLAC audio codec classes (available on all platforms).
If your app doesn't need to read FLAC files, you might want to disable this to
reduce the size of your codebase and build time.
*/
#define JUCE_USE_FLAC 1
/** Config: JUCE_USE_OGGVORBIS
Enables the Ogg-Vorbis audio codec classes (available on all platforms).
If your app doesn't need to read Ogg-Vorbis files, you might want to disable this to
reduce the size of your codebase and build time.
*/
#define JUCE_USE_OGGVORBIS 1
/** Config: JUCE_USE_MP3AUDIOFORMAT
Enables the software-based MP3AudioFormat class.
IMPORTANT DISCLAIMER: By choosing to enable the JUCE_USE_MP3AUDIOFORMAT flag and to compile
this MP3 code into your software, you do so AT YOUR OWN RISK! By doing so, you are agreeing
that Raw Material Software is in no way responsible for any patent, copyright, or other
legal issues that you may suffer as a result.
The code in juce_MP3AudioFormat.cpp is NOT guaranteed to be free from infringements of 3rd-party
intellectual property. If you wish to use it, please seek your own independent advice about the
legality of doing so. If you are not willing to accept full responsibility for the consequences
of using this code, then do not enable this setting.
*/
#define JUCE_USE_MP3AUDIOFORMAT 0
/** Config: JUCE_USE_LAME_AUDIO_FORMAT
Enables the LameEncoderAudioFormat class.
*/
#define JUCE_USE_LAME_AUDIO_FORMAT 0
/** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT
Enables the Windows Media SDK codecs.
*/
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 0
//=============================================================================
// juce_audio_processors
/** Config: JUCE_PLUGINHOST_VST
Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be
installed on your machine.
@see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3
*/
#define JUCE_PLUGINHOST_VST 1
/** Config: JUCE_PLUGINHOST_VST3
Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be
installed on your machine.
@see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU
*/
#if 0 //MAC || WINDOWS
#define JUCE_PLUGINHOST_VST3 1
#else
#define JUCE_PLUGINHOST_VST3 0
#endif
/** Config: JUCE_PLUGINHOST_AU
Enables the AudioUnit plugin hosting classes. This is Mac-only, of course.
@see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_VST3
*/
#if MAC
#define JUCE_PLUGINHOST_AU 1
#else
#define JUCE_PLUGINHOST_AU 0
#endif
#if LINUX
#define JUCE_PLUGINHOST_LADSPA 1
#else
#define JUCE_PLUGINHOST_LADSPA 0
#endif
//=============================================================================
// juce_audio_utils
//=============================================================================
// juce_core
/** Config: JUCE_FORCE_DEBUG
Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings,
but if you define this value, you can override this to force it to be true or false.
*/
#define JUCE_FORCE_DEBUG 0
/** Config: JUCE_LOG_ASSERTIONS
If this flag is enabled, the the jassert and jassertfalse macros will always use Logger::writeToLog()
to write a message when an assertion happens.
Enabling it will also leave this turned on in release builds. When it's disabled,
however, the jassert and jassertfalse macros will not be compiled in a
release build.
@see jassert, jassertfalse, Logger
*/
#define JUCE_LOG_ASSERTIONS 1
/** Config: JUCE_CHECK_MEMORY_LEAKS
Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector
class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes.
*/
#ifdef DEBUG
#define JUCE_CHECK_MEMORY_LEAKS 1
#else
#define JUCE_CHECK_MEMORY_LEAKS 0
#endif
/** Config: JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
In a Visual C++ build, this can be used to stop the required system libs being
automatically added to the link stage.
*/
#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0
/* Config: JUCE_INCLUDE_ZLIB_CODE
This can be used to disable Juce's embedded 3rd-party zlib code.
You might need to tweak this if you're linking to an external zlib library in your app,
but for normal apps, this option should be left alone.
If you disable this, you might also want to set a value for JUCE_ZLIB_INCLUDE_PATH, to
specify the path where your zlib headers live.
*/
#define JUCE_INCLUDE_ZLIB_CODE 1
/** Config: JUCE_USE_CURL
Enables http/https support via libcurl (Linux only). Enabling this will add an additional
run-time dynmic dependency to libcurl.
If you disable this then https/ssl support will not be available on linux.
*/
#define JUCE_USE_CURL 0
/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS
If enabled, this will add some exception-catching code to forward unhandled exceptions
to your JUCEApplication::unhandledException() callback.
*/
#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0
/** Config: JUCE_ALLOW_STATIC_NULL_VARIABLES
If disabled, this will turn off dangerous static globals like String::empty, var::null, etc
which can cause nasty order-of-initialisation problems if they are referenced during static
constructor code.
*/
#define JUCE_ALLOW_STATIC_NULL_VARIABLES 0
#define JUCE_STRING_UTF_TYPE 8
#define JUCE_USE_VFORK 1
//=============================================================================
// juce_cryptography
//=============================================================================
// juce_data_structures
//=============================================================================
// juce_dsp
/** Config: JUCE_ASSERTION_FIRFILTER
When this flag is enabled, an assertion will be generated during the
execution of DEBUG configurations if you use a FIRFilter class to process
FIRCoefficients with a size higher than 128, to tell you that's it would be
more efficient to use the Convolution class instead. It is enabled by
default, but you may want to disable it if you really want to process such
a filter in the time domain.
*/
#define JUCE_ASSERTION_FIRFILTER 0
/** Config: JUCE_DSP_USE_INTEL_MKL
If this flag is set, then JUCE will use Intel's MKL for JUCE's FFT and
convolution classes.
The folder containing the mkl_dfti.h header must be in your header
search paths when using this flag. You also need to add all the necessary
intel mkl libraries to the "External Libraries to Link" field in the
Projucer.
*/
#define JUCE_DSP_USE_INTEL_MKL 0
/** Config: JUCE_DSP_USE_SHARED_FFTW
If this flag is set, then JUCE will search for the fftw shared libraries
at runtime and use the library for JUCE's FFT and convolution classes.
If the library is not found, then JUCE's fallback FFT routines will be used.
This is especially useful on linux as fftw often comes pre-installed on
popular linux distros.
You must respect the FFTW license when enabling this option.
*/
#define JUCE_DSP_USE_SHARED_FFTW 0
/** Config: JUCE_DSP_USE_STATIC_FFTW
If this flag is set, then JUCE will use the statically linked fftw libraries
for JUCE's FFT and convolution classes.
You must add the fftw header/library folder to the extra header/library search
paths of your JUCE project. You also need to add the fftw library itself
to the extra libraries supplied to your JUCE project during linking.
You must respect the FFTW license when enabling this option.
*/
#define JUCE_DSP_USE_STATIC_FFTW 0
/** Config: JUCE_DSP_ENABLE_SNAP_TO_ZERO
Enables code in the dsp module to avoid floating point denormals during the
processing of some of the dsp module's filters.
Enabling this will add a slight performance overhead to the DSP module's
filters and algorithms. If your audio app already disables denormals altogether
(for exmaple, by using the ScopedNoDenormals class or the
FloatVectorOperations::disableDenormalisedNumberSupport method), then you
can safely disable this flag to shave off a few cpu cycles from the DSP module's
filters and algorithms.
*/
#define JUCE_DSP_ENABLE_SNAP_TO_ZERO 0
//=============================================================================
// juce_events
//=============================================================================
// juce_graphics
//=============================================================================
/** Config: JUCE_USE_COREIMAGE_LOADER
On OSX, enabling this flag means that the CoreImage codecs will be used to load
PNG/JPEG/GIF files. It is enabled by default, but you may want to disable it if
you'd rather use libpng, libjpeg, etc.
*/
#if MAC
#define JUCE_USE_COREIMAGE_LOADER 1
#else
#define JUCE_USE_COREIMAGE_LOADER 0
#endif
/** Config: JUCE_USE_DIRECTWRITE
Enabling this flag means that DirectWrite will be used when available for font
management and layout.
*/
#define JUCE_USE_DIRECTWRITE 0
#define JUCE_INCLUDE_PNGLIB_CODE 1
#define JUCE_INCLUDE_JPEGLIB_CODE 1
#if MAC
#define USE_COREGRAPHICS_RENDERING 1
#else
#define USE_COREGRAPHICS_RENDERING 0
#endif
//=============================================================================
// juce_gui_basics
/** Config: JUCE_ENABLE_REPAINT_DEBUGGING
If this option is turned on, each area of the screen that gets repainted will
flash in a random colour, so that you can see exactly which bits of your
components are being drawn.
*/
#define JUCE_ENABLE_REPAINT_DEBUGGING 0
/** JUCE_USE_XRANDR: Enables Xrandr multi-monitor support (Linux only).
Unless you specifically want to disable this, it's best to leave this option turned on.
Note that your users do not need to have Xrandr installed for your JUCE app to run, as
the availability of Xrandr is queried during runtime.
*/
#define JUCE_USE_XRANDR 0
/** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only).
Unless you specifically want to disable this, it's best to leave this option turned on.
*/
#define JUCE_USE_XINERAMA 0
/** Config: JUCE_USE_XSHM
Enables X shared memory for faster rendering on Linux. This is best left turned on
unless you have a good reason to disable it.
*/
#if LINUX
#define JUCE_USE_XSHM 1
#else
#define JUCE_USE_XSHM 0
#endif
/** Config: JUCE_USE_XRENDER
Enables XRender to allow semi-transparent windowing on Linux.
*/
#define JUCE_USE_XRENDER 0
/** Config: JUCE_USE_XCURSOR
Uses XCursor to allow ARGB cursor on Linux. This is best left turned on unless you have
a good reason to disable it.
*/
#if LINUX
#define JUCE_USE_XCURSOR 1
#else
#define JUCE_USE_XCURSOR 0
#endif
//=============================================================================
// juce_gui_extra
/** Config: JUCE_WEB_BROWSER
This lets you disable the WebBrowserComponent class (Mac and Windows).
If you're not using any embedded web-pages, turning this off may reduce your code size.
*/
#define JUCE_WEB_BROWSER 0
/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR
This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support. See the documentation
for that macro for more details.
*/
#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 0
//=============================================================================
// juce_opengl
//=============================================================================
// drowaudio
/** Config: DROWAUDIO_USE_FFTREAL
Enables the FFTReal library. By default this is enabled except on the Mac
where the Accelerate framework is preferred. However, if you do explicity
enable this setting fftreal can be used for testing purposes.
*/
#if JUCE_MAC
#define DROWAUDIO_USE_FFTREAL 0
#else
#define DROWAUDIO_USE_FFTREAL 1
#endif
/** Config: DROWAUDIO_USE_SOUNDTOUCH
Enables the SoundTouch library and the associated SoundTouch classes for
independant pitch and tempo scaling. By default this is enabled.
*/
#define DROWAUDIO_USE_SOUNDTOUCH 1
/** Config: DROWAUDIO_USE_CURL
Enables the cURL library and the associated network classes. By default
this is enabled.
*/
#define DROWAUDIO_USE_CURL 0
//=============================================================================
// juced
/** Config: JUCE_LASH
Enables LASH support on Linux.
Not enabled by default.
*/
#define JUCE_LASH 0
/** Config: JUCE_USE_GLX
Enable this under Linux to use GLX for fast openGL rendering with alpha
compositing support over a composite manager (compiz / xcompmgr).
Not enabled by default.
*/
#define JUCE_USE_GLX 0
/** Config: JUCE_SUPPORT_SQLITE
Setting this allows the build to use SQLITE libraries for access a self-contained,
serverless, zero-configuration, transactional SQL database engine.
Not enabled by default.
*/
#define JUCE_SUPPORT_SQLITE 0
/** Config: JUCE_SUPPORT_SCRIPTING
Setting this allows the build to use Angelscript library for using scripting
inside the juce library itself
Not enabled by default.
*/
#define JUCE_SUPPORT_SCRIPTING 0
/** Config: JUCETICE_INCLUDE_ANGELSCRIPT_CODE
Enables direct inclusion of the angelscript library.
Enabled by default.
*/
#define JUCETICE_INCLUDE_ANGELSCRIPT_CODE 0
/** Config: JUCETICE_INCLUDE_CURL_CODE
Enables direct inclusion of curl.
// Currently not available //
*/
#define JUCETICE_INCLUDE_CURL_CODE 0
//=============================================================================
// Linux embed build
#if JUCE_AUDIOPROCESSOR_NO_GUI
#undef JUCE_MODULE_AVAILABLE_juce_graphics
#undef JUCE_MODULE_AVAILABLE_juce_gui_basics
#undef JUCE_MODULE_AVAILABLE_juce_gui_extra
#undef JUCE_MODULE_AVAILABLE_juce_opengl
#define JUCE_MODULE_AVAILABLE_juce_graphics 0
#define JUCE_MODULE_AVAILABLE_juce_gui_basics 0
#define JUCE_MODULE_AVAILABLE_juce_gui_extra 0
#define JUCE_MODULE_AVAILABLE_juce_opengl 0
#undef JUCE_ALSA
#undef JUCE_PLUGINHOST_LADSPA
#undef JUCE_PLUGINHOST_VST
#define JUCE_ALSA 0
#define JUCE_PLUGINHOST_LADSPA 0
#define JUCE_PLUGINHOST_VST 0
#endif
#endif // BUILD_JUCE_APPCONFIG_H_INCLUDED

+ 49
- 0
libs/juce-current/meson.build View File

@@ -0,0 +1,49 @@
###############################################################################

if linux_embed
juce_current_srcs = [
'source/modules/juce_audio_basics/juce_audio_basics.cpp',
'source/modules/juce_audio_devices/juce_audio_devices.cpp',
'source/modules/juce_audio_formats/juce_audio_formats.cpp',
'source/modules/juce_audio_processors/juce_audio_processors.cpp',
'source/modules/juce_audio_utils/juce_audio_utils.cpp',
'source/modules/juce_core/juce_core.cpp',
'source/modules/juce_cryptography/juce_cryptography.cpp',
'source/modules/juce_data_structures/juce_data_structures.cpp',
'source/modules/juce_dsp/juce_dsp.cpp',
'source/modules/juce_events/juce_events.cpp',
]
else
juce_current_srcs = [
'source/modules/juce_audio_basics/juce_audio_basics.cpp',
'source/modules/juce_audio_devices/juce_audio_devices.cpp',
'source/modules/juce_audio_formats/juce_audio_formats.cpp',
'source/modules/juce_audio_processors/juce_audio_processors.cpp',
'source/modules/juce_audio_utils/juce_audio_utils.cpp',
'source/modules/juce_core/juce_core.cpp',
'source/modules/juce_cryptography/juce_cryptography.cpp',
'source/modules/juce_data_structures/juce_data_structures.cpp',
'source/modules/juce_dsp/juce_dsp.cpp',
'source/modules/juce_events/juce_events.cpp',
'source/modules/juce_graphics/juce_graphics.cpp',
'source/modules/juce_gui_basics/juce_gui_basics.cpp',
'source/modules/juce_gui_extra/juce_gui_extra.cpp',
'source/modules/juce_opengl/juce_opengl.cpp',
]
endif

lib_juce_current = static_library('juce-current',
sources: juce_current_srcs,
include_directories: [
include_directories('.'),
include_directories('source'),
include_directories('source' / 'modules'),
include_directories('..' / 'juced' / 'source' / 'dependancies' / 'ladspa_sdk' / 'src'),
],
cpp_args: build_flags_cpp,
dependencies: dependencies,
pic: true,
install: false,
)

###############################################################################

+ 157
- 0
libs/juce-current/source/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h View File

@@ -0,0 +1,157 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A subclass of AudioPlayHead can supply information about the position and
status of a moving play head during audio playback.
One of these can be supplied to an AudioProcessor object so that it can find
out about the position of the audio that it is rendering.
@see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead
@tags{Audio}
*/
class JUCE_API AudioPlayHead
{
protected:
//==============================================================================
AudioPlayHead() = default;
public:
virtual ~AudioPlayHead() = default;
//==============================================================================
/** Frame rate types. */
enum FrameRateType
{
fps23976 = 0,
fps24 = 1,
fps25 = 2,
fps2997 = 3,
fps30 = 4,
fps2997drop = 5,
fps30drop = 6,
fps60 = 7,
fps60drop = 8,
fpsUnknown = 99
};
//==============================================================================
/** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method.
*/
struct JUCE_API CurrentPositionInfo
{
/** The tempo in BPM */
double bpm;
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */
int timeSigNumerator;
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */
int timeSigDenominator;
/** The current play position, in samples from the start of the timeline. */
int64 timeInSamples;
/** The current play position, in seconds from the start of the timeline. */
double timeInSeconds;
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */
double editOriginTime;
/** The current play position, in units of quarter-notes. */
double ppqPosition;
/** The position of the start of the last bar, in units of quarter-notes.
This is the time from the start of the timeline to the start of the current
bar, in ppq units.
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If
it's not available, the value will be 0.
*/
double ppqPositionOfLastBarStart;
/** The video frame rate, if applicable. */
FrameRateType frameRate;
/** True if the transport is currently playing. */
bool isPlaying;
/** True if the transport is currently recording.
(When isRecording is true, then isPlaying will also be true).
*/
bool isRecording;
/** The current cycle start position in units of quarter-notes.
Note that not all hosts or plugin formats may provide this value.
@see isLooping
*/
double ppqLoopStart;
/** The current cycle end position in units of quarter-notes.
Note that not all hosts or plugin formats may provide this value.
@see isLooping
*/
double ppqLoopEnd;
/** True if the transport is currently looping. */
bool isLooping;
//==============================================================================
bool operator== (const CurrentPositionInfo& other) const noexcept;
bool operator!= (const CurrentPositionInfo& other) const noexcept;
void resetToDefault();
};
//==============================================================================
/** Fills-in the given structure with details about the transport's
position at the start of the current processing block. If this method returns
false then the current play head position is not available and the given
structure will be undefined.
You can ONLY call this from your processBlock() method! Calling it at other
times will produce undefined behaviour, as the host may not have any context
in which a time would make sense, and some hosts will almost certainly have
multithreading issues if it's not called on the audio thread.
*/
virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0;
/** Returns true if this object can control the transport. */
virtual bool canControlTransport() { return false; }
/** Starts or stops the audio. */
virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); }
/** Starts or stops recording the audio. */
virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); }
/** Rewinds the audio. */
virtual void transportRewind() {}
};
} // namespace juce

+ 723
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioChannelSet.cpp View File

@@ -0,0 +1,723 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast<int64> (c))
{
}
AudioChannelSet::AudioChannelSet (const Array<ChannelType>& c)
{
for (auto channel : c)
addChannel (channel);
}
bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; }
bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; }
bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; }
String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type)
{
if (type >= discreteChannel0)
return "Discrete " + String (type - discreteChannel0 + 1);
switch (type)
{
case left: return NEEDS_TRANS("Left");
case right: return NEEDS_TRANS("Right");
case centre: return NEEDS_TRANS("Centre");
case LFE: return NEEDS_TRANS("LFE");
case leftSurround: return NEEDS_TRANS("Left Surround");
case rightSurround: return NEEDS_TRANS("Right Surround");
case leftCentre: return NEEDS_TRANS("Left Centre");
case rightCentre: return NEEDS_TRANS("Right Centre");
case centreSurround: return NEEDS_TRANS("Centre Surround");
case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear");
case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear");
case topMiddle: return NEEDS_TRANS("Top Middle");
case topFrontLeft: return NEEDS_TRANS("Top Front Left");
case topFrontCentre: return NEEDS_TRANS("Top Front Centre");
case topFrontRight: return NEEDS_TRANS("Top Front Right");
case topRearLeft: return NEEDS_TRANS("Top Rear Left");
case topRearCentre: return NEEDS_TRANS("Top Rear Centre");
case topRearRight: return NEEDS_TRANS("Top Rear Right");
case wideLeft: return NEEDS_TRANS("Wide Left");
case wideRight: return NEEDS_TRANS("Wide Right");
case LFE2: return NEEDS_TRANS("LFE 2");
case leftSurroundSide: return NEEDS_TRANS("Left Surround Side");
case rightSurroundSide: return NEEDS_TRANS("Right Surround Side");
case ambisonicW: return NEEDS_TRANS("Ambisonic W");
case ambisonicX: return NEEDS_TRANS("Ambisonic X");
case ambisonicY: return NEEDS_TRANS("Ambisonic Y");
case ambisonicZ: return NEEDS_TRANS("Ambisonic Z");
case topSideLeft: return NEEDS_TRANS("Top Side Left");
case topSideRight: return NEEDS_TRANS("Top Side Right");
case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4");
case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5");
case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6");
case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7");
case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8");
case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9");
case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10");
case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11");
case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12");
case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13");
case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14");
case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15");
case ambisonicACN16: return NEEDS_TRANS("Ambisonic 16");
case ambisonicACN17: return NEEDS_TRANS("Ambisonic 17");
case ambisonicACN18: return NEEDS_TRANS("Ambisonic 18");
case ambisonicACN19: return NEEDS_TRANS("Ambisonic 19");
case ambisonicACN20: return NEEDS_TRANS("Ambisonic 20");
case ambisonicACN21: return NEEDS_TRANS("Ambisonic 21");
case ambisonicACN22: return NEEDS_TRANS("Ambisonic 22");
case ambisonicACN23: return NEEDS_TRANS("Ambisonic 23");
case ambisonicACN24: return NEEDS_TRANS("Ambisonic 24");
case ambisonicACN25: return NEEDS_TRANS("Ambisonic 25");
case ambisonicACN26: return NEEDS_TRANS("Ambisonic 26");
case ambisonicACN27: return NEEDS_TRANS("Ambisonic 27");
case ambisonicACN28: return NEEDS_TRANS("Ambisonic 28");
case ambisonicACN29: return NEEDS_TRANS("Ambisonic 29");
case ambisonicACN30: return NEEDS_TRANS("Ambisonic 30");
case ambisonicACN31: return NEEDS_TRANS("Ambisonic 31");
case ambisonicACN32: return NEEDS_TRANS("Ambisonic 32");
case ambisonicACN33: return NEEDS_TRANS("Ambisonic 33");
case ambisonicACN34: return NEEDS_TRANS("Ambisonic 34");
case ambisonicACN35: return NEEDS_TRANS("Ambisonic 35");
case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left");
case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre");
case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right");
case proximityLeft: return NEEDS_TRANS("Proximity Left");
case proximityRight: return NEEDS_TRANS("Proximity Right");
case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left");
case bottomSideRight: return NEEDS_TRANS("Bottom Side Right");
case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left");
case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre");
case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right");
case discreteChannel0:
case unknown:
default: break;
}
return "Unknown";
}
String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type)
{
if (type >= discreteChannel0)
return String (type - discreteChannel0 + 1);
switch (type)
{
case left: return "L";
case right: return "R";
case centre: return "C";
case LFE: return "Lfe";
case leftSurround: return "Ls";
case rightSurround: return "Rs";
case leftCentre: return "Lc";
case rightCentre: return "Rc";
case centreSurround: return "Cs";
case leftSurroundRear: return "Lrs";
case rightSurroundRear: return "Rrs";
case topMiddle: return "Tm";
case topFrontLeft: return "Tfl";
case topFrontCentre: return "Tfc";
case topFrontRight: return "Tfr";
case topRearLeft: return "Trl";
case topRearCentre: return "Trc";
case topRearRight: return "Trr";
case wideLeft: return "Wl";
case wideRight: return "Wr";
case LFE2: return "Lfe2";
case leftSurroundSide: return "Lss";
case rightSurroundSide: return "Rss";
case ambisonicACN0: return "ACN0";
case ambisonicACN1: return "ACN1";
case ambisonicACN2: return "ACN2";
case ambisonicACN3: return "ACN3";
case ambisonicACN4: return "ACN4";
case ambisonicACN5: return "ACN5";
case ambisonicACN6: return "ACN6";
case ambisonicACN7: return "ACN7";
case ambisonicACN8: return "ACN8";
case ambisonicACN9: return "ACN9";
case ambisonicACN10: return "ACN10";
case ambisonicACN11: return "ACN11";
case ambisonicACN12: return "ACN12";
case ambisonicACN13: return "ACN13";
case ambisonicACN14: return "ACN14";
case ambisonicACN15: return "ACN15";
case ambisonicACN16: return "ACN16";
case ambisonicACN17: return "ACN17";
case ambisonicACN18: return "ACN18";
case ambisonicACN19: return "ACN19";
case ambisonicACN20: return "ACN20";
case ambisonicACN21: return "ACN21";
case ambisonicACN22: return "ACN22";
case ambisonicACN23: return "ACN23";
case ambisonicACN24: return "ACN24";
case ambisonicACN25: return "ACN25";
case ambisonicACN26: return "ACN26";
case ambisonicACN27: return "ACN27";
case ambisonicACN28: return "ACN28";
case ambisonicACN29: return "ACN29";
case ambisonicACN30: return "ACN30";
case ambisonicACN31: return "ACN31";
case ambisonicACN32: return "ACN32";
case ambisonicACN33: return "ACN33";
case ambisonicACN34: return "ACN34";
case ambisonicACN35: return "ACN35";
case topSideLeft: return "Tsl";
case topSideRight: return "Tsr";
case bottomFrontLeft: return "Bfl";
case bottomFrontCentre: return "Bfc";
case bottomFrontRight: return "Bfr";
case proximityLeft: return "Pl";
case proximityRight: return "Pr";
case bottomSideLeft: return "Bsl";
case bottomSideRight: return "Bsr";
case bottomRearLeft: return "Brl";
case bottomRearCentre: return "Brc";
case bottomRearRight: return "Brr";
case discreteChannel0:
case unknown:
default: break;
}
if (type >= ambisonicACN4 && type <= ambisonicACN35)
return "ACN" + String (type - ambisonicACN4 + 4);
return {};
}
AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr)
{
if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9'))
return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (discreteChannel0)
+ abbr.getIntValue() - 1);
if (abbr == "L") return left;
if (abbr == "R") return right;
if (abbr == "C") return centre;
if (abbr == "Lfe") return LFE;
if (abbr == "Ls") return leftSurround;
if (abbr == "Rs") return rightSurround;
if (abbr == "Lc") return leftCentre;
if (abbr == "Rc") return rightCentre;
if (abbr == "Cs") return centreSurround;
if (abbr == "Lrs") return leftSurroundRear;
if (abbr == "Rrs") return rightSurroundRear;
if (abbr == "Tm") return topMiddle;
if (abbr == "Tfl") return topFrontLeft;
if (abbr == "Tfc") return topFrontCentre;
if (abbr == "Tfr") return topFrontRight;
if (abbr == "Trl") return topRearLeft;
if (abbr == "Trc") return topRearCentre;
if (abbr == "Trr") return topRearRight;
if (abbr == "Wl") return wideLeft;
if (abbr == "Wr") return wideRight;
if (abbr == "Lfe2") return LFE2;
if (abbr == "Lss") return leftSurroundSide;
if (abbr == "Rss") return rightSurroundSide;
if (abbr == "W") return ambisonicW;
if (abbr == "X") return ambisonicX;
if (abbr == "Y") return ambisonicY;
if (abbr == "Z") return ambisonicZ;
if (abbr == "ACN0") return ambisonicACN0;
if (abbr == "ACN1") return ambisonicACN1;
if (abbr == "ACN2") return ambisonicACN2;
if (abbr == "ACN3") return ambisonicACN3;
if (abbr == "ACN4") return ambisonicACN4;
if (abbr == "ACN5") return ambisonicACN5;
if (abbr == "ACN6") return ambisonicACN6;
if (abbr == "ACN7") return ambisonicACN7;
if (abbr == "ACN8") return ambisonicACN8;
if (abbr == "ACN9") return ambisonicACN9;
if (abbr == "ACN10") return ambisonicACN10;
if (abbr == "ACN11") return ambisonicACN11;
if (abbr == "ACN12") return ambisonicACN12;
if (abbr == "ACN13") return ambisonicACN13;
if (abbr == "ACN14") return ambisonicACN14;
if (abbr == "ACN15") return ambisonicACN15;
if (abbr == "ACN16") return ambisonicACN16;
if (abbr == "ACN17") return ambisonicACN17;
if (abbr == "ACN18") return ambisonicACN18;
if (abbr == "ACN19") return ambisonicACN19;
if (abbr == "ACN20") return ambisonicACN20;
if (abbr == "ACN21") return ambisonicACN21;
if (abbr == "ACN22") return ambisonicACN22;
if (abbr == "ACN23") return ambisonicACN23;
if (abbr == "ACN24") return ambisonicACN24;
if (abbr == "ACN25") return ambisonicACN25;
if (abbr == "ACN26") return ambisonicACN26;
if (abbr == "ACN27") return ambisonicACN27;
if (abbr == "ACN28") return ambisonicACN28;
if (abbr == "ACN29") return ambisonicACN29;
if (abbr == "ACN30") return ambisonicACN30;
if (abbr == "ACN31") return ambisonicACN31;
if (abbr == "ACN32") return ambisonicACN32;
if (abbr == "ACN33") return ambisonicACN33;
if (abbr == "ACN34") return ambisonicACN34;
if (abbr == "ACN35") return ambisonicACN35;
if (abbr == "Tsl") return topSideLeft;
if (abbr == "Tsr") return topSideRight;
if (abbr == "Bfl") return bottomFrontLeft;
if (abbr == "Bfc") return bottomFrontCentre;
if (abbr == "Bfr") return bottomFrontRight;
if (abbr == "Bsl") return bottomSideLeft;
if (abbr == "Bsr") return bottomSideRight;
if (abbr == "Brl") return bottomRearLeft;
if (abbr == "Brc") return bottomRearCentre;
if (abbr == "Brr") return bottomRearRight;
return unknown;
}
String AudioChannelSet::getSpeakerArrangementAsString() const
{
StringArray speakerTypes;
for (auto& speaker : getChannelTypes())
{
auto name = getAbbreviatedChannelTypeName (speaker);
if (name.isNotEmpty())
speakerTypes.add (name);
}
return speakerTypes.joinIntoString (" ");
}
AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str)
{
AudioChannelSet set;
for (auto& abbr : StringArray::fromTokens (str, true))
{
auto type = getChannelTypeFromAbbreviation (abbr);
if (type != unknown)
set.addChannel (type);
}
return set;
}
String AudioChannelSet::getDescription() const
{
if (isDiscreteLayout()) return "Discrete #" + String (size());
if (*this == disabled()) return "Disabled";
if (*this == mono()) return "Mono";
if (*this == stereo()) return "Stereo";
if (*this == createLCR()) return "LCR";
if (*this == createLRS()) return "LRS";
if (*this == createLCRS()) return "LCRS";
if (*this == create5point0()) return "5.0 Surround";
if (*this == create5point1()) return "5.1 Surround";
if (*this == create6point0()) return "6.0 Surround";
if (*this == create6point1()) return "6.1 Surround";
if (*this == create6point0Music()) return "6.0 (Music) Surround";
if (*this == create6point1Music()) return "6.1 (Music) Surround";
if (*this == create7point0()) return "7.0 Surround";
if (*this == create7point1()) return "7.1 Surround";
if (*this == create7point0SDDS()) return "7.0 Surround SDDS";
if (*this == create7point1SDDS()) return "7.1 Surround SDDS";
if (*this == create7point0point2()) return "7.0.2 Surround";
if (*this == create7point1point2()) return "7.1.2 Surround";
if (*this == quadraphonic()) return "Quadraphonic";
if (*this == pentagonal()) return "Pentagonal";
if (*this == hexagonal()) return "Hexagonal";
if (*this == octagonal()) return "Octagonal";
// ambisonics
{
auto order = getAmbisonicOrder();
if (order >= 0)
{
String suffix;
switch (order)
{
case 1: suffix = "st"; break;
case 2: suffix = "nd"; break;
case 3: suffix = "rd"; break;
default: suffix = "th"; break;
}
return String (order) + suffix + " Order Ambisonics";
}
}
return "Unknown";
}
bool AudioChannelSet::isDiscreteLayout() const noexcept
{
for (auto& speaker : getChannelTypes())
if (speaker <= ambisonicACN35)
return false;
return true;
}
int AudioChannelSet::size() const noexcept
{
return channels.countNumberOfSetBits();
}
AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept
{
int bit = channels.findNextSetBit(0);
for (int i = 0; i < index && bit >= 0; ++i)
bit = channels.findNextSetBit (bit + 1);
return static_cast<ChannelType> (bit);
}
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept
{
int idx = 0;
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1))
{
if (static_cast<ChannelType> (bit) == type)
return idx;
idx++;
}
return -1;
}
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const
{
Array<ChannelType> result;
for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1))
result.add (static_cast<ChannelType> (bit));
return result;
}
void AudioChannelSet::addChannel (ChannelType newChannel)
{
const int bit = static_cast<int> (newChannel);
jassert (bit >= 0 && bit < 1024);
channels.setBit (bit);
}
void AudioChannelSet::removeChannel (ChannelType newChannel)
{
const int bit = static_cast<int> (newChannel);
jassert (bit >= 0 && bit < 1024);
channels.clearBit (bit);
}
AudioChannelSet AudioChannelSet::disabled() { return {}; }
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); }
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); }
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); }
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); }
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); }
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); }
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround)); }
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); }
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); }
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); }
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); }
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); }
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); }
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); }
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << centreSurround) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); }
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); }
AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); }
AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); }
AudioChannelSet AudioChannelSet::ambisonic (int order)
{
jassert (isPositiveAndBelow (order, 6));
if (order == 0)
return AudioChannelSet ((uint32) (1 << ambisonicACN0));
AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3));
auto numAmbisonicChannels = (order + 1) * (order + 1);
set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true);
return set;
}
int AudioChannelSet::getAmbisonicOrder() const
{
auto ambisonicOrder = getAmbisonicOrderForNumChannels (size());
if (ambisonicOrder >= 0)
return (*this == ambisonic (ambisonicOrder) ? ambisonicOrder : -1);
return -1;
}
AudioChannelSet AudioChannelSet::discreteChannels (int numChannels)
{
AudioChannelSet s;
s.channels.setRange (discreteChannel0, numChannels, true);
return s;
}
AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels)
{
if (numChannels == 1) return AudioChannelSet::mono();
if (numChannels == 2) return AudioChannelSet::stereo();
if (numChannels == 3) return AudioChannelSet::createLCR();
if (numChannels == 4) return AudioChannelSet::quadraphonic();
if (numChannels == 5) return AudioChannelSet::create5point0();
if (numChannels == 6) return AudioChannelSet::create5point1();
if (numChannels == 7) return AudioChannelSet::create7point0();
if (numChannels == 8) return AudioChannelSet::create7point1();
return discreteChannels (numChannels);
}
AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels)
{
if (numChannels == 1) return AudioChannelSet::mono();
if (numChannels == 2) return AudioChannelSet::stereo();
if (numChannels == 3) return AudioChannelSet::createLCR();
if (numChannels == 4) return AudioChannelSet::quadraphonic();
if (numChannels == 5) return AudioChannelSet::create5point0();
if (numChannels == 6) return AudioChannelSet::create5point1();
if (numChannels == 7) return AudioChannelSet::create7point0();
if (numChannels == 8) return AudioChannelSet::create7point1();
return {};
}
Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels)
{
Array<AudioChannelSet> retval;
if (numChannels != 0)
{
retval.add (AudioChannelSet::discreteChannels (numChannels));
if (numChannels == 1)
{
retval.add (AudioChannelSet::mono());
}
else if (numChannels == 2)
{
retval.add (AudioChannelSet::stereo());
}
else if (numChannels == 3)
{
retval.add (AudioChannelSet::createLCR());
retval.add (AudioChannelSet::createLRS());
}
else if (numChannels == 4)
{
retval.add (AudioChannelSet::quadraphonic());
retval.add (AudioChannelSet::createLCRS());
}
else if (numChannels == 5)
{
retval.add (AudioChannelSet::create5point0());
retval.add (AudioChannelSet::pentagonal());
}
else if (numChannels == 6)
{
retval.add (AudioChannelSet::create5point1());
retval.add (AudioChannelSet::create6point0());
retval.add (AudioChannelSet::create6point0Music());
retval.add (AudioChannelSet::hexagonal());
}
else if (numChannels == 7)
{
retval.add (AudioChannelSet::create7point0());
retval.add (AudioChannelSet::create7point0SDDS());
retval.add (AudioChannelSet::create6point1());
retval.add (AudioChannelSet::create6point1Music());
}
else if (numChannels == 8)
{
retval.add (AudioChannelSet::create7point1());
retval.add (AudioChannelSet::create7point1SDDS());
retval.add (AudioChannelSet::octagonal());
}
auto order = getAmbisonicOrderForNumChannels (numChannels);
if (order >= 0)
retval.add (AudioChannelSet::ambisonic (order));
}
return retval;
}
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array<ChannelType>& channelArray)
{
AudioChannelSet set;
for (auto ch : channelArray)
{
jassert (! set.channels[static_cast<int> (ch)]);
set.addChannel (ch);
}
return set;
}
//==============================================================================
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask)
{
return AudioChannelSet (static_cast<uint32> ((dwChannelMask & ((1 << 18) - 1)) << 1));
}
int32 AudioChannelSet::getWaveChannelMask() const noexcept
{
if (channels.getHighestBit() > topRearRight)
return -1;
return (channels.toInteger() >> 1);
}
//==============================================================================
int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels)
{
auto sqrtMinusOne = std::sqrt (static_cast<float> (numChannels)) - 1.0f;
auto ambisonicOrder = jmax (0, static_cast<int> (std::floor (sqrtMinusOne)));
if (ambisonicOrder > 5)
return -1;
return (static_cast<float> (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1);
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class AudioChannelSetUnitTest : public UnitTest
{
public:
AudioChannelSetUnitTest()
: UnitTest ("AudioChannelSetUnitTest", UnitTestCategories::audio)
{}
void runTest() override
{
auto max = AudioChannelSet::maxChannelsOfNamedLayout;
beginTest ("maxChannelsOfNamedLayout is non-discrete");
expect (AudioChannelSet::channelSetsWithNumberOfChannels (max).size() >= 2);
beginTest ("channelSetsWithNumberOfChannels returns correct speaker count");
{
for (auto ch = 1; ch <= max; ++ch)
{
auto channelSets = AudioChannelSet::channelSetsWithNumberOfChannels (ch);
for (auto set : channelSets)
expect (set.size() == ch);
}
}
beginTest ("Ambisonics");
{
uint64 mask = 0;
mask |= (1ull << AudioChannelSet::ambisonicACN0);
checkAmbisonic (mask, 0, "0th Order Ambisonics");
mask |= (1ull << AudioChannelSet::ambisonicACN1) | (1ull << AudioChannelSet::ambisonicACN2) | (1ull << AudioChannelSet::ambisonicACN3);
checkAmbisonic (mask, 1, "1st Order Ambisonics");
mask |= (1ull << AudioChannelSet::ambisonicACN4) | (1ull << AudioChannelSet::ambisonicACN5) | (1ull << AudioChannelSet::ambisonicACN6)
| (1ull << AudioChannelSet::ambisonicACN7) | (1ull << AudioChannelSet::ambisonicACN8);
checkAmbisonic (mask, 2, "2nd Order Ambisonics");
mask |= (1ull << AudioChannelSet::ambisonicACN9) | (1ull << AudioChannelSet::ambisonicACN10) | (1ull << AudioChannelSet::ambisonicACN11)
| (1ull << AudioChannelSet::ambisonicACN12) | (1ull << AudioChannelSet::ambisonicACN13) | (1ull << AudioChannelSet::ambisonicACN14)
| (1ull << AudioChannelSet::ambisonicACN15);
checkAmbisonic (mask, 3, "3rd Order Ambisonics");
mask |= (1ull << AudioChannelSet::ambisonicACN16) | (1ull << AudioChannelSet::ambisonicACN17) | (1ull << AudioChannelSet::ambisonicACN18)
| (1ull << AudioChannelSet::ambisonicACN19) | (1ull << AudioChannelSet::ambisonicACN20) | (1ull << AudioChannelSet::ambisonicACN21)
| (1ull << AudioChannelSet::ambisonicACN22) | (1ull << AudioChannelSet::ambisonicACN23) | (1ull << AudioChannelSet::ambisonicACN24);
checkAmbisonic (mask, 4, "4th Order Ambisonics");
mask |= (1ull << AudioChannelSet::ambisonicACN25) | (1ull << AudioChannelSet::ambisonicACN26) | (1ull << AudioChannelSet::ambisonicACN27)
| (1ull << AudioChannelSet::ambisonicACN28) | (1ull << AudioChannelSet::ambisonicACN29) | (1ull << AudioChannelSet::ambisonicACN30)
| (1ull << AudioChannelSet::ambisonicACN31) | (1ull << AudioChannelSet::ambisonicACN32) | (1ull << AudioChannelSet::ambisonicACN33)
| (1ull << AudioChannelSet::ambisonicACN34) | (1ull << AudioChannelSet::ambisonicACN35);
checkAmbisonic (mask, 5, "5th Order Ambisonics");
}
}
private:
void checkAmbisonic (uint64 mask, int order, const char* layoutName)
{
auto expected = AudioChannelSet::ambisonic (order);
auto numChannels = expected.size();
expect (numChannels == BigInteger ((int64) mask).countNumberOfSetBits());
expect (channelSetFromMask (mask) == expected);
expect (order == expected.getAmbisonicOrder());
expect (expected.getDescription() == layoutName);
auto layouts = AudioChannelSet::channelSetsWithNumberOfChannels (numChannels);
expect (layouts.contains (expected));
for (auto layout : layouts)
expect (layout.getAmbisonicOrder() == (layout == expected ? order : -1));
}
static AudioChannelSet channelSetFromMask (uint64 mask)
{
Array<AudioChannelSet::ChannelType> channels;
for (int bit = 0; bit <= 62; ++bit)
if ((mask & (1ull << bit)) != 0)
channels.add (static_cast<AudioChannelSet::ChannelType> (bit));
return AudioChannelSet::channelSetWithChannels (channels);
}
};
static AudioChannelSetUnitTest audioChannelSetUnitTest;
#endif
} // namespace juce

+ 484
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioChannelSet.h View File

@@ -0,0 +1,484 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a set of audio channel types.
For example, you might have a set of left + right channels, which is a stereo
channel set. It is a collection of values from the AudioChannelSet::ChannelType
enum, where each type may only occur once within the set.
The documentation below lists which AudioChannelSet corresponds to which native
layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio
are particularly confusing. For example, the layout which is labeled as "7.1 SDDS"
in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas
AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's
kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag
as an indication to the actual layout of the speakers.
@see Bus
@tags{Audio}
*/
class JUCE_API AudioChannelSet
{
public:
/** Creates an empty channel set.
You can call addChannel to add channels to the set.
*/
AudioChannelSet() = default;
/** Creates a zero-channel set which can be used to indicate that a
bus is disabled. */
static AudioChannelSet JUCE_CALLTYPE disabled();
//==============================================================================
/** Creates a one-channel mono set (centre).
Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE mono();
/** Creates a set containing a stereo set (left, right).
Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE stereo();
//==============================================================================
/** Creates a set containing an LCR set (left, right, centre).
Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio)
This format is referred to as "LRC" in Cubase.
This format is referred to as "LCR" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE createLCR();
/** Creates a set containing an LRS set (left, right, surround).
Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio)
This format is referred to as "LRS" in Cubase.
*/
static AudioChannelSet JUCE_CALLTYPE createLRS();
/** Creates a set containing an LCRS set (left, right, centre, surround).
Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio)
This format is referred to as "LCRS (Pro Logic)" in Logic Pro.
This format is referred to as "LRCS" in Cubase.
This format is referred to as "LCRS" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE createLCRS();
//==============================================================================
/** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio)
This format is referred to as "5.0" in Cubase.
This format is referred to as "5.0" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create5point0();
/** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE).
Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio)
This format is referred to as "5.1 (ITU 775)" in Logic Pro.
This format is referred to as "5.1" in Cubase.
This format is referred to as "5.1" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create5point1();
/** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround).
Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio)
Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)".
This format is referred to as "6.0 Cine" in Cubase.
This format is referred to as "6.0" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create6point0();
/** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE).
Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio)
This format is referred to as "6.1" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create6point1();
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide).
Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio)
This format is referred to as "6.0 Music" in Cubase.
*/
static AudioChannelSet JUCE_CALLTYPE create6point0Music();
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE).
Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE create6point1Music();
/** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear).
Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio)
This format is referred to as "7.0" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create7point0();
/** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre).
Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio)
This format is referred to as "7.0 SDDS" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS();
/** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE).
Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio)
This format is referred to as "7.1 (3/4.1)" in Logic Pro.
This format is referred to as "7.1" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create7point1();
/** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE).
Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio)
This format is referred to as "7.1 (SDDS)" in Logic Pro.
This format is referred to as "7.1 SDDS" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS();
/** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight).
Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE create7point0point2();
/** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight).
Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), n/a (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE create7point1point2();
//==============================================================================
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround)
Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio)
This format is referred to as "Quadraphonic" in Logic Pro.
This format is referred to as "Quadro" in Cubase.
This format is referred to as "Quad" in Pro Tools.
*/
static AudioChannelSet JUCE_CALLTYPE quadraphonic();
/** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear).
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE pentagonal();
/** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre).
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE hexagonal();
/** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight).
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE octagonal();
//==============================================================================
/** Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order.
Is equivalent to: kAmbiXXXOrderACN (VST), AAX_eStemFormat_Ambi_XXX_ACN (AAX), kAudioChannelLayoutTag_HOA_ACN_SN3D (CoreAudio)
*/
static AudioChannelSet JUCE_CALLTYPE ambisonic (int order = 1);
/** Returns the order of the ambisonic layout represented by this AudioChannelSet. If the
AudioChannelSet is not an ambisonic layout, then this method will return -1.
*/
int getAmbisonicOrder() const;
//==============================================================================
/** Creates a set of untyped discrete channels. */
static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels);
/** Create a canonical channel set for a given number of channels.
For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels);
/** Create a channel set for a given number of channels which is non-discrete.
If numChannels is larger than the number of channels of the surround format
with the maximum amount of channels (currently 7.1 Surround), then this
function returns an empty set.*/
static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels);
/** Return an array of channel sets which have a given number of channels */
static Array<AudioChannelSet> JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels);
//==============================================================================
/** Represents different audio channel types. */
enum ChannelType
{
unknown = 0, /**< Unknown channel type. */
//==============================================================================
left = 1, /**< L channel. */
right = 2, /**< R channel. */
centre = 3, /**< C channel. (Sometimes M for mono) */
//==============================================================================
LFE = 4, /**< LFE channel. */
leftSurround = 5, /**< Ls channel. */
rightSurround = 6, /**< Rs channel. */
leftCentre = 7, /**< Lc (AAX/VST), Lc used as Lss in AU for most layouts. */
rightCentre = 8, /**< Rc (AAX/VST), Rc used as Rss in AU for most layouts. */
centreSurround = 9, /**< Cs/S channel. */
surround = centreSurround, /**< Same as Centre Surround channel. */
leftSurroundSide = 10, /**< Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) channel. */
rightSurroundSide = 11, /**< Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) channel. */
topMiddle = 12, /**< Top Middle channel. */
topFrontLeft = 13, /**< Top Front Left channel. */
topFrontCentre = 14, /**< Top Front Centre channel. */
topFrontRight = 15, /**< Top Front Right channel. */
topRearLeft = 16, /**< Top Rear Left channel. */
topRearCentre = 17, /**< Top Rear Centre channel. */
topRearRight = 18, /**< Top Rear Right channel. */
LFE2 = 19, /**< Second LFE channel. */
leftSurroundRear = 20, /**< Lsr (AAX), Lcs (VST), Rls (AU) channel. */
rightSurroundRear = 21, /**< Rsr (AAX), Rcs (VST), Rrs (AU) channel. */
wideLeft = 22, /**< Wide Left channel. */
wideRight = 23, /**< Wide Right channel. */
//==============================================================================
// Used by Dolby Atmos 7.0.2 and 7.1.2
topSideLeft = 28, /**< Lts (AAX), Tsl (VST) channel for Dolby Atmos. */
topSideRight = 29, /**< Rts (AAX), Tsr (VST) channel for Dolby Atmos. */
//==============================================================================
// Ambisonic ACN formats - all channels are SN3D normalised
// zero-th and first-order ambisonic ACN
ambisonicACN0 = 24, /**< Zero-th ambisonic channel number 0. */
ambisonicACN1 = 25, /**< First-order ambisonic channel number 1. */
ambisonicACN2 = 26, /**< First-order ambisonic channel number 2. */
ambisonicACN3 = 27, /**< First-order ambisonic channel number 3. */
// second-order ambisonic
ambisonicACN4 = 30, /**< Second-order ambisonic channel number 4. */
ambisonicACN5 = 31, /**< Second-order ambisonic channel number 5. */
ambisonicACN6 = 32, /**< Second-order ambisonic channel number 6. */
ambisonicACN7 = 33, /**< Second-order ambisonic channel number 7. */
ambisonicACN8 = 34, /**< Second-order ambisonic channel number 8. */
// third-order ambisonic
ambisonicACN9 = 35, /**< Third-order ambisonic channel number 9. */
ambisonicACN10 = 36, /**< Third-order ambisonic channel number 10. */
ambisonicACN11 = 37, /**< Third-order ambisonic channel number 11. */
ambisonicACN12 = 38, /**< Third-order ambisonic channel number 12. */
ambisonicACN13 = 39, /**< Third-order ambisonic channel number 13. */
ambisonicACN14 = 40, /**< Third-order ambisonic channel number 14. */
ambisonicACN15 = 41, /**< Third-order ambisonic channel number 15. */
// fourth-order ambisonic
ambisonicACN16 = 42, /**< Fourth-order ambisonic channel number 16. */
ambisonicACN17 = 43, /**< Fourth-order ambisonic channel number 17. */
ambisonicACN18 = 44, /**< Fourth-order ambisonic channel number 18. */
ambisonicACN19 = 45, /**< Fourth-order ambisonic channel number 19. */
ambisonicACN20 = 46, /**< Fourth-order ambisonic channel number 20. */
ambisonicACN21 = 47, /**< Fourth-order ambisonic channel number 21. */
ambisonicACN22 = 48, /**< Fourth-order ambisonic channel number 22. */
ambisonicACN23 = 49, /**< Fourth-order ambisonic channel number 23. */
ambisonicACN24 = 50, /**< Fourth-order ambisonic channel number 24. */
// fifth-order ambisonic
ambisonicACN25 = 51, /**< Fifth-order ambisonic channel number 25. */
ambisonicACN26 = 52, /**< Fifth-order ambisonic channel number 26. */
ambisonicACN27 = 53, /**< Fifth-order ambisonic channel number 27. */
ambisonicACN28 = 54, /**< Fifth-order ambisonic channel number 28. */
ambisonicACN29 = 55, /**< Fifth-order ambisonic channel number 29. */
ambisonicACN30 = 56, /**< Fifth-order ambisonic channel number 30. */
ambisonicACN31 = 57, /**< Fifth-order ambisonic channel number 31. */
ambisonicACN32 = 58, /**< Fifth-order ambisonic channel number 32. */
ambisonicACN33 = 59, /**< Fifth-order ambisonic channel number 33. */
ambisonicACN34 = 60, /**< Fifth-order ambisonic channel number 34. */
ambisonicACN35 = 61, /**< Fifth-order ambisonic channel number 35. */
//==============================================================================
ambisonicW = ambisonicACN0, /**< Same as zero-th ambisonic channel number 0. */
ambisonicX = ambisonicACN3, /**< Same as first-order ambisonic channel number 3. */
ambisonicY = ambisonicACN1, /**< Same as first-order ambisonic channel number 1. */
ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */
//==============================================================================
bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */
bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */
bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */
proximityLeft = 65, /**< Proximity Left (Pl) */
proximityRight = 66, /**< Proximity Right (Pr) */
bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */
bottomSideRight = 68, /**< Bottom Side Right (Bsr) */
bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */
bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */
bottomRearRight = 71, /**< Bottom Rear Right (Brr) */
//==============================================================================
discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */
};
/** Returns the name of a given channel type. For example, this method may return "Surround Left". */
static String JUCE_CALLTYPE getChannelTypeName (ChannelType);
/** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */
static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType);
/** Returns the channel type from an abbreviated name. */
static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation);
//==============================================================================
enum
{
maxChannelsOfNamedLayout = 36
};
/** Adds a channel to the set. */
void addChannel (ChannelType newChannelType);
/** Removes a channel from the set. */
void removeChannel (ChannelType newChannelType);
/** Returns the number of channels in the set. */
int size() const noexcept;
/** Returns true if there are no channels in the set. */
bool isDisabled() const noexcept { return size() == 0; }
/** Returns an array of all the types in this channel set. */
Array<ChannelType> getChannelTypes() const;
/** Returns the type of one of the channels in the set, by index. */
ChannelType getTypeOfChannel (int channelIndex) const noexcept;
/** Returns the index for a particular channel-type.
Will return -1 if the this set does not contain a channel of this type. */
int getChannelIndexForType (ChannelType type) const noexcept;
/** Returns a string containing a whitespace-separated list of speaker types
corresponding to each channel. For example in a 5.1 arrangement,
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown,
the returned string will be empty.*/
String getSpeakerArrangementAsString() const;
/** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString
@see getSpeakerArrangementAsString */
static AudioChannelSet fromAbbreviatedString (const String& set);
/** Returns the description of the current layout. For example, this method may return
"Quadraphonic". Note that the returned string may not be unique. */
String getDescription() const;
/** Returns if this is a channel layout made-up of discrete channels. */
bool isDiscreteLayout() const noexcept;
/** Intersect two channel layouts. */
void intersect (const AudioChannelSet& other) { channels &= other.channels; }
/** Creates a channel set for a list of channel types. This function will assert
if you supply a duplicate channel.
Note that this method ignores the order in which the channels are given, i.e.
two arrays with the same elements but in a different order will still result
in the same channel set.
*/
static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array<ChannelType>&);
//==============================================================================
// Conversion between wave and juce channel layout identifiers
/** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used
in .wav files). */
static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask);
/** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav
files) of the receiver.
Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask
representation.
*/
int32 getWaveChannelMask() const noexcept;
//==============================================================================
bool operator== (const AudioChannelSet&) const noexcept;
bool operator!= (const AudioChannelSet&) const noexcept;
bool operator< (const AudioChannelSet&) const noexcept;
private:
//==============================================================================
BigInteger channels;
//==============================================================================
explicit AudioChannelSet (uint32);
explicit AudioChannelSet (const Array<ChannelType>&);
//==============================================================================
static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int);
};
} // namespace juce

+ 596
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioDataConverters.cpp View File

@@ -0,0 +1,596 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fffff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
}
}
}
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fffff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
}
}
}
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fffffff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
auto maxVal = (double) 0x7fffffff;
auto intData = static_cast<char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
char* d = static_cast<char*> (dest);
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<float*> (d) = source[i];
#if JUCE_BIG_ENDIAN
*reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d));
#endif
d += destBytesPerSample;
}
}
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample)
{
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
auto d = static_cast<char*> (dest);
for (int i = 0; i < numSamples; ++i)
{
*reinterpret_cast<float*> (d) = source[i];
#if JUCE_LITTLE_ENDIAN
*reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d));
#endif
d += destBytesPerSample;
}
}
//==============================================================================
void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint16*> (intData));
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint16*> (intData));
}
}
}
void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData));
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData));
}
}
}
void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
}
}
}
void AudioDataConverters::convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
}
}
}
void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / (float) 0x7fffffff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData));
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData));
}
}
}
void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
const float scale = 1.0f / (float) 0x7fffffff;
auto intData = static_cast<const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData));
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData));
}
}
}
void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
auto s = static_cast<const char*> (source);
for (int i = 0; i < numSamples; ++i)
{
dest[i] = *reinterpret_cast<const float*> (s);
#if JUCE_BIG_ENDIAN
auto d = reinterpret_cast<uint32*> (dest + i);
*d = ByteOrder::swap (*d);
#endif
s += srcBytesPerSample;
}
}
void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample)
{
auto s = static_cast<const char*> (source);
for (int i = 0; i < numSamples; ++i)
{
dest[i] = *reinterpret_cast<const float*> (s);
#if JUCE_LITTLE_ENDIAN
auto d = reinterpret_cast<uint32*> (dest + i);
*d = ByteOrder::swap (*d);
#endif
s += srcBytesPerSample;
}
}
//==============================================================================
void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, int numSamples)
{
switch (destFormat)
{
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break;
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break;
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break;
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break;
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break;
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break;
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break;
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break;
default: jassertfalse; break;
}
}
void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const void* source, float* dest, int numSamples)
{
switch (sourceFormat)
{
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break;
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break;
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break;
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break;
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break;
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break;
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break;
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break;
default: jassertfalse; break;
}
}
//==============================================================================
void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels)
{
for (int chan = 0; chan < numChannels; ++chan)
{
auto i = chan;
auto src = source [chan];
for (int j = 0; j < numSamples; ++j)
{
dest [i] = src [j];
i += numChannels;
}
}
}
void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels)
{
for (int chan = 0; chan < numChannels; ++chan)
{
auto i = chan;
auto dst = dest [chan];
for (int j = 0; j < numSamples; ++j)
{
dst [j] = source [i];
i += numChannels;
}
}
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class AudioConversionTests : public UnitTest
{
public:
AudioConversionTests()
: UnitTest ("Audio data conversion", UnitTestCategories::audio)
{}
template <class F1, class E1, class F2, class E2>
struct Test5
{
static void test (UnitTest& unitTest, Random& r)
{
test (unitTest, false, r);
test (unitTest, true, r);
}
static void test (UnitTest& unitTest, bool inPlace, Random& r)
{
const int numSamples = 2048;
int32 original [(size_t) numSamples],
converted[(size_t) numSamples],
reversed [(size_t) numSamples];
{
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original);
bool clippingFailed = false;
for (int i = 0; i < numSamples / 2; ++i)
{
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f);
if (! d.isFloatingPoint())
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed;
++d;
d.setAsInt32 (r.nextInt());
++d;
}
unitTest.expect (! clippingFailed);
}
// convert data from the source to dest format..
std::unique_ptr<AudioData::Converter> conv (new AudioData::ConverterInstance<AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>,
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst>>());
conv->convertSamples (inPlace ? reversed : converted, original, numSamples);
// ..and back again..
conv.reset (new AudioData::ConverterInstance<AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>,
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst>>());
if (! inPlace)
zeromem (reversed, sizeof (reversed));
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples);
{
int biggestDiff = 0;
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original);
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed);
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution()
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution();
for (int i = 0; i < numSamples; ++i)
{
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32()));
++d1;
++d2;
}
unitTest.expect (biggestDiff <= errorMargin);
}
}
};
template <class F1, class E1, class FormatType>
struct Test3
{
static void test (UnitTest& unitTest, Random& r)
{
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest, r);
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest, r);
}
};
template <class FormatType, class Endianness>
struct Test2
{
static void test (UnitTest& unitTest, Random& r)
{
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest, r);
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest, r);
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest, r);
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest, r);
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest, r);
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest, r);
}
};
template <class FormatType>
struct Test1
{
static void test (UnitTest& unitTest, Random& r)
{
Test2 <FormatType, AudioData::BigEndian>::test (unitTest, r);
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest, r);
}
};
void runTest() override
{
auto r = getRandom();
beginTest ("Round-trip conversion: Int8");
Test1 <AudioData::Int8>::test (*this, r);
beginTest ("Round-trip conversion: Int16");
Test1 <AudioData::Int16>::test (*this, r);
beginTest ("Round-trip conversion: Int24");
Test1 <AudioData::Int24>::test (*this, r);
beginTest ("Round-trip conversion: Int32");
Test1 <AudioData::Int32>::test (*this, r);
beginTest ("Round-trip conversion: Float32");
Test1 <AudioData::Float32>::test (*this, r);
}
};
static AudioConversionTests audioConversionUnitTests;
#endif
} // namespace juce

+ 716
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioDataConverters.h View File

@@ -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

+ 75
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp View File

@@ -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

+ 96
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h View File

@@ -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

+ 1194
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
File diff suppressed because it is too large
View File


+ 1305
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
File diff suppressed because it is too large
View File


+ 257
- 0
libs/juce-current/source/juce_audio_basics/buffers/juce_FloatVectorOperations.h View File

@@ -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

+ 88
- 0
libs/juce-current/source/juce_audio_basics/juce_audio_basics.cpp View File

@@ -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"

+ 122
- 0
libs/juce-current/source/juce_audio_basics/juce_audio_basics.h View File

@@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_audio_basics
vendor: juce
version: 6.0.0
name: JUCE audio and MIDI data classes
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc.
website: http://www.juce.com/juce
license: ISC
dependencies: juce_core
OSXFrameworks: Accelerate
iOSFrameworks: Accelerate
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_AUDIO_BASICS_H_INCLUDED
#include <juce_core/juce_core.h>
//==============================================================================
#undef Complex // apparently some C libraries actually define these symbols (!)
#undef Factor
//==============================================================================
#if JUCE_MINGW && ! defined (__SSE2__)
#define JUCE_USE_SSE_INTRINSICS 0
#endif
#ifndef JUCE_USE_SSE_INTRINSICS
#define JUCE_USE_SSE_INTRINSICS 1
#endif
#if ! JUCE_INTEL
#undef JUCE_USE_SSE_INTRINSICS
#endif
#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON))
#define JUCE_USE_ARM_NEON 1
#endif
#if TARGET_IPHONE_SIMULATOR
#ifdef JUCE_USE_ARM_NEON
#undef JUCE_USE_ARM_NEON
#endif
#define JUCE_USE_ARM_NEON 0
#endif
//==============================================================================
#include "buffers/juce_AudioDataConverters.h"
#include "buffers/juce_FloatVectorOperations.h"
#include "buffers/juce_AudioSampleBuffer.h"
#include "buffers/juce_AudioChannelSet.h"
#include "buffers/juce_AudioProcessLoadMeasurer.h"
#include "utilities/juce_Decibels.h"
#include "utilities/juce_IIRFilter.h"
#include "utilities/juce_GenericInterpolator.h"
#include "utilities/juce_Interpolators.h"
#include "utilities/juce_SmoothedValue.h"
#include "utilities/juce_Reverb.h"
#include "utilities/juce_ADSR.h"
#include "midi/juce_MidiMessage.h"
#include "midi/juce_MidiBuffer.h"
#include "midi/juce_MidiMessageSequence.h"
#include "midi/juce_MidiFile.h"
#include "midi/juce_MidiKeyboardState.h"
#include "midi/juce_MidiRPN.h"
#include "mpe/juce_MPEValue.h"
#include "mpe/juce_MPENote.h"
#include "mpe/juce_MPEZoneLayout.h"
#include "mpe/juce_MPEInstrument.h"
#include "mpe/juce_MPEMessages.h"
#include "mpe/juce_MPESynthesiserBase.h"
#include "mpe/juce_MPESynthesiserVoice.h"
#include "mpe/juce_MPESynthesiser.h"
#include "mpe/juce_MPEUtils.h"
#include "sources/juce_AudioSource.h"
#include "sources/juce_PositionableAudioSource.h"
#include "sources/juce_BufferingAudioSource.h"
#include "sources/juce_ChannelRemappingAudioSource.h"
#include "sources/juce_IIRFilterAudioSource.h"
#include "sources/juce_MemoryAudioSource.h"
#include "sources/juce_MixerAudioSource.h"
#include "sources/juce_ResamplingAudioSource.h"
#include "sources/juce_ReverbAudioSource.h"
#include "sources/juce_ToneGeneratorAudioSource.h"
#include "synthesisers/juce_Synthesiser.h"
#include "audio_play_head/juce_AudioPlayHead.h"

+ 23
- 0
libs/juce-current/source/juce_audio_basics/juce_audio_basics.mm View File

@@ -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"

+ 240
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiBuffer.cpp View File

@@ -0,0 +1,240 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace MidiBufferHelpers
{
inline int getEventTime (const void* d) noexcept
{
return readUnaligned<int32> (d);
}
inline uint16 getEventDataSize (const void* d) noexcept
{
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32));
}
inline uint16 getEventTotalSize (const void* d) noexcept
{
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16));
}
static int findActualEventLength (const uint8* data, int maxBytes) noexcept
{
auto byte = (unsigned int) *data;
if (byte == 0xf0 || byte == 0xf7)
{
int i = 1;
while (i < maxBytes)
if (data[i++] == 0xf7)
break;
return i;
}
if (byte == 0xff)
{
if (maxBytes == 1)
return 1;
int n;
auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n);
return jmin (maxBytes, n + 2 + bytesLeft);
}
if (byte >= 0x80)
return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte));
return 0;
}
static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept
{
while (d < endData && getEventTime (d) <= samplePosition)
d += getEventTotalSize (d);
return d;
}
}
//==============================================================================
MidiBufferIterator& MidiBufferIterator::operator++() noexcept
{
data += sizeof (int32) + sizeof (uint16) + size_t (MidiBufferHelpers::getEventDataSize (data));
return *this;
}
MidiBufferIterator MidiBufferIterator::operator++ (int) noexcept
{
auto copy = *this;
++(*this);
return copy;
}
MidiBufferIterator::reference MidiBufferIterator::operator*() const noexcept
{
return { data + sizeof (int32) + sizeof (uint16),
MidiBufferHelpers::getEventDataSize (data),
MidiBufferHelpers::getEventTime (data) };
}
//==============================================================================
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept
{
addEvent (message, 0);
}
void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); }
void MidiBuffer::clear() noexcept { data.clearQuick(); }
void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); }
bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; }
void MidiBuffer::clear (int startSample, int numSamples)
{
auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1);
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1);
data.removeRange ((int) (start - data.begin()), (int) (end - data.begin()));
}
void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber)
{
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
}
void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber)
{
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes);
if (numBytes > 0)
{
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16);
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin());
data.insertMultiple (offset, 0, (int) newItemSize);
auto* d = data.begin() + offset;
writeUnaligned<int32> (d, sampleNumber);
d += sizeof (int32);
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes));
d += sizeof (uint16);
memcpy (d, newData, (size_t) numBytes);
}
}
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
int startSample, int numSamples, int sampleDeltaToAdd)
{
for (auto i = otherBuffer.findNextSamplePosition (startSample); i != otherBuffer.cend(); ++i)
{
const auto metadata = *i;
if (metadata.samplePosition >= startSample + numSamples && numSamples >= 0)
break;
addEvent (metadata.data, metadata.numBytes, metadata.samplePosition + sampleDeltaToAdd);
}
}
int MidiBuffer::getNumEvents() const noexcept
{
int n = 0;
auto end = data.end();
for (auto d = data.begin(); d < end; ++n)
d += MidiBufferHelpers::getEventTotalSize (d);
return n;
}
int MidiBuffer::getFirstEventTime() const noexcept
{
return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0;
}
int MidiBuffer::getLastEventTime() const noexcept
{
if (data.size() == 0)
return 0;
auto endData = data.end();
for (auto d = data.begin();;)
{
auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d);
if (nextOne >= endData)
return MidiBufferHelpers::getEventTime (d);
d = nextOne;
}
}
MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const noexcept
{
return std::find_if (cbegin(), cend(), [&] (const MidiMessageMetadata& metadata) noexcept
{
return metadata.samplePosition >= samplePosition;
});
}
//==============================================================================
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept
: buffer (b), iterator (b.data.begin())
{
}
MidiBuffer::Iterator::~Iterator() noexcept {}
void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept
{
iterator = buffer.findNextSamplePosition (samplePosition);
}
bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept
{
if (iterator == buffer.cend())
return false;
const auto metadata = *iterator++;
midiData = metadata.data;
numBytes = metadata.numBytes;
samplePosition = metadata.samplePosition;
return true;
}
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept
{
if (iterator == buffer.cend())
return false;
const auto metadata = *iterator++;
result = metadata.getMessage();
samplePosition = metadata.samplePosition;
return true;
}
} // namespace juce

+ 346
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiBuffer.h View File

@@ -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

+ 446
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiFile.cpp View File

@@ -0,0 +1,446 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace MidiFileHelpers
{
static void writeVariableLengthInt (OutputStream& out, uint32 v)
{
auto buffer = v & 0x7f;
while ((v >>= 7) != 0)
{
buffer <<= 8;
buffer |= ((v & 0x7f) | 0x80);
}
for (;;)
{
out.writeByte ((char) buffer);
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
}
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
{
auto ch = ByteOrder::bigEndianInt (data);
data += 4;
if (ch != ByteOrder::bigEndianInt ("MThd"))
{
bool ok = false;
if (ch == ByteOrder::bigEndianInt ("RIFF"))
{
for (int i = 0; i < 8; ++i)
{
ch = ByteOrder::bigEndianInt (data);
data += 4;
if (ch == ByteOrder::bigEndianInt ("MThd"))
{
ok = true;
break;
}
}
}
if (! ok)
return false;
}
auto bytesRemaining = ByteOrder::bigEndianInt (data);
data += 4;
fileType = (short) ByteOrder::bigEndianShort (data);
data += 2;
numberOfTracks = (short) ByteOrder::bigEndianShort (data);
data += 2;
timeFormat = (short) ByteOrder::bigEndianShort (data);
data += 2;
bytesRemaining -= 6;
data += bytesRemaining;
return true;
}
static double convertTicksToSeconds (double time,
const MidiMessageSequence& tempoEvents,
int timeFormat)
{
if (timeFormat < 0)
return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
double lastTime = 0, correctedTime = 0;
auto tickLen = 1.0 / (timeFormat & 0x7fff);
auto secsPerTick = 0.5 * tickLen;
auto numEvents = tempoEvents.getNumEvents();
for (int i = 0; i < numEvents; ++i)
{
auto& m = tempoEvents.getEventPointer(i)->message;
auto eventTime = m.getTimeStamp();
if (eventTime >= time)
break;
correctedTime += (eventTime - lastTime) * secsPerTick;
lastTime = eventTime;
if (m.isTempoMetaEvent())
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
while (i + 1 < numEvents)
{
auto& m2 = tempoEvents.getEventPointer(i + 1)->message;
if (m2.getTimeStamp() != eventTime)
break;
if (m2.isTempoMetaEvent())
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
++i;
}
}
return correctedTime + (time - lastTime) * secsPerTick;
}
template <typename MethodType>
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
MidiMessageSequence& results,
MethodType method)
{
for (auto* track : tracks)
{
auto numEvents = track->getNumEvents();
for (int j = 0; j < numEvents; ++j)
{
auto& m = track->getEventPointer(j)->message;
if ((m.*method)())
results.addEvent (m);
}
}
}
}
//==============================================================================
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
MidiFile::~MidiFile() {}
MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
{
tracks.addCopiesOf (other.tracks);
}
MidiFile& MidiFile::operator= (const MidiFile& other)
{
tracks.clear();
tracks.addCopiesOf (other.tracks);
timeFormat = other.timeFormat;
return *this;
}
MidiFile::MidiFile (MidiFile&& other)
: tracks (std::move (other.tracks)),
timeFormat (other.timeFormat)
{
}
MidiFile& MidiFile::operator= (MidiFile&& other)
{
tracks = std::move (other.tracks);
timeFormat = other.timeFormat;
return *this;
}
void MidiFile::clear()
{
tracks.clear();
}
//==============================================================================
int MidiFile::getNumTracks() const noexcept
{
return tracks.size();
}
const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
{
return tracks[index];
}
void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
{
tracks.add (new MidiMessageSequence (trackSequence));
}
//==============================================================================
short MidiFile::getTimeFormat() const noexcept
{
return timeFormat;
}
void MidiFile::setTicksPerQuarterNote (int ticks) noexcept
{
timeFormat = (short) ticks;
}
void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept
{
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
}
//==============================================================================
void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const
{
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
}
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const
{
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
}
void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const
{
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
}
double MidiFile::getLastTimestamp() const
{
double t = 0.0;
for (auto* ms : tracks)
t = jmax (t, ms->getEndTime());
return t;
}
//==============================================================================
bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs)
{
clear();
MemoryBlock data;
const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
// (put a sanity-check on the file size, as midi files are generally small)
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
{
auto size = data.getSize();
auto d = static_cast<const uint8*> (data.getData());
short fileType, expectedTracks;
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
{
size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
int track = 0;
for (;;)
{
auto chunkType = (int) ByteOrder::bigEndianInt (d);
d += 4;
auto chunkSize = (int) ByteOrder::bigEndianInt (d);
d += 4;
if (chunkSize <= 0 || (size_t) chunkSize > size)
break;
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
readNextTrack (d, chunkSize, createMatchingNoteOffs);
if (++track >= expectedTracks)
break;
size -= (size_t) chunkSize + 8;
d += chunkSize;
}
return true;
}
}
return false;
}
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
{
double time = 0;
uint8 lastStatusByte = 0;
MidiMessageSequence result;
while (size > 0)
{
int bytesUsed;
auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
data += bytesUsed;
size -= bytesUsed;
time += delay;
int messSize = 0;
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
if (messSize <= 0)
break;
size -= messSize;
data += messSize;
result.addEvent (mm);
auto firstByte = *(mm.getRawData());
if ((firstByte & 0xf0) != 0xf0)
lastStatusByte = firstByte;
}
// sort so that we put all the note-offs before note-ons that have the same time
std::stable_sort (result.list.begin(), result.list.end(),
[] (const MidiMessageSequence::MidiEventHolder* a,
const MidiMessageSequence::MidiEventHolder* b)
{
auto t1 = a->message.getTimeStamp();
auto t2 = b->message.getTimeStamp();
if (t1 < t2) return true;
if (t2 < t1) return false;
return a->message.isNoteOff() && b->message.isNoteOn();
});
addTrack (result);
if (createMatchingNoteOffs)
tracks.getLast()->updateMatchedPairs();
}
//==============================================================================
void MidiFile::convertTimestampTicksToSeconds()
{
MidiMessageSequence tempoEvents;
findAllTempoEvents (tempoEvents);
findAllTimeSigEvents (tempoEvents);
if (timeFormat != 0)
{
for (auto* ms : tracks)
{
for (int j = ms->getNumEvents(); --j >= 0;)
{
auto& m = ms->getEventPointer(j)->message;
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
}
}
}
}
//==============================================================================
bool MidiFile::writeTo (OutputStream& out, int midiFileType) const
{
jassert (midiFileType >= 0 && midiFileType <= 2);
if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
if (! out.writeIntBigEndian (6)) return false;
if (! out.writeShortBigEndian ((short) midiFileType)) return false;
if (! out.writeShortBigEndian ((short) tracks.size())) return false;
if (! out.writeShortBigEndian (timeFormat)) return false;
for (auto* ms : tracks)
if (! writeTrack (out, *ms))
return false;
out.flush();
return true;
}
bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
{
MemoryOutputStream out;
int lastTick = 0;
uint8 lastStatusByte = 0;
bool endOfTrackEventWritten = false;
for (int i = 0; i < ms.getNumEvents(); ++i)
{
auto& mm = ms.getEventPointer(i)->message;
if (mm.isEndOfTrackMetaEvent())
endOfTrackEventWritten = true;
auto tick = roundToInt (mm.getTimeStamp());
auto delta = jmax (0, tick - lastTick);
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
lastTick = tick;
auto* data = mm.getRawData();
auto dataSize = mm.getRawDataSize();
auto statusByte = data[0];
if (statusByte == lastStatusByte
&& (statusByte & 0xf0) != 0xf0
&& dataSize > 1
&& i > 0)
{
++data;
--dataSize;
}
else if (statusByte == 0xf0) // Write sysex message with length bytes.
{
out.writeByte ((char) statusByte);
++data;
--dataSize;
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
}
out.write (data, (size_t) dataSize);
lastStatusByte = statusByte;
}
if (! endOfTrackEventWritten)
{
out.writeByte (0); // (tick delta)
auto m = MidiMessage::endOfTrack();
out.write (m.getRawData(), (size_t) m.getRawDataSize());
}
if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
mainOut << out;
return true;
}
} // namespace juce

+ 197
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiFile.h View File

@@ -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

+ 177
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiKeyboardState.cpp View File

@@ -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

+ 196
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiKeyboardState.h View File

@@ -0,0 +1,196 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a piano keyboard, keeping track of which keys are currently pressed.
This object can parse a stream of midi events, using them to update its idea
of which keys are pressed for each individual midi channel.
When keys go up or down, it can broadcast these events to listener objects.
It also allows key up/down events to be triggered with its noteOn() and noteOff()
methods, and midi messages for these events will be merged into the
midi stream that gets processed by processNextMidiBuffer().
@tags{Audio}
*/
class JUCE_API MidiKeyboardState
{
public:
//==============================================================================
MidiKeyboardState();
~MidiKeyboardState();
//==============================================================================
/** Resets the state of the object.
All internal data for all the channels is reset, but no events are sent as a
result.
If you want to release any keys that are currently down, and to send out note-up
midi messages for this, use the allNotesOff() method instead.
*/
void reset();
/** Returns true if the given midi key is currently held down for the given midi channel.
The channel number must be between 1 and 16. If you want to see if any notes are
on for a range of channels, use the isNoteOnForChannels() method.
*/
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept;
/** Returns true if the given midi key is currently held down on any of a set of midi channels.
The channel mask has a bit set for each midi channel you want to test for - bit
0 = midi channel 1, bit 1 = midi channel 2, etc.
If a note is on for at least one of the specified channels, this returns true.
*/
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept;
/** Turns a specified note on.
This will cause a suitable midi note-on event to be injected into the midi buffer during the
next call to processNextMidiBuffer().
It will also trigger a synchronous callback to the listeners to tell them that the key has
gone down.
*/
void noteOn (int midiChannel, int midiNoteNumber, float velocity);
/** Turns a specified note off.
This will cause a suitable midi note-off event to be injected into the midi buffer during the
next call to processNextMidiBuffer().
It will also trigger a synchronous callback to the listeners to tell them that the key has
gone up.
But if the note isn't actually down for the given channel, this method will in fact do nothing.
*/
void noteOff (int midiChannel, int midiNoteNumber, float velocity);
/** This will turn off any currently-down notes for the given midi channel.
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels.
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks
and events being added to the midi stream.
*/
void allNotesOff (int midiChannel);
//==============================================================================
/** Looks at a key-up/down event and uses it to update the state of this object.
To process a buffer full of midi messages, use the processNextMidiBuffer() method
instead.
*/
void processNextMidiEvent (const MidiMessage& message);
/** Scans a midi stream for up/down events and adds its own events to it.
This will look for any up/down events and use them to update the internal state,
synchronously making suitable callbacks to the listeners.
If injectIndirectEvents is true, then midi events to produce the recent noteOn()
and noteOff() calls will be added into the buffer.
Only the section of the buffer whose timestamps are between startSample and
(startSample + numSamples) will be affected, and any events added will be placed
between these times.
If you're going to use this method, you'll need to keep calling it regularly for
it to work satisfactorily.
To process a single midi event at a time, use the processNextMidiEvent() method
instead.
*/
void processNextMidiBuffer (MidiBuffer& buffer,
int startSample,
int numSamples,
bool injectIndirectEvents);
//==============================================================================
/** Receives events from a MidiKeyboardState object. */
class Listener
{
public:
//==============================================================================
virtual ~Listener() = default;
//==============================================================================
/** Called when one of the MidiKeyboardState's keys is pressed.
This will be called synchronously when the state is either processing a
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
when a note is being played with its MidiKeyboardState::noteOn() method.
Note that this callback could happen from an audio callback thread, so be
careful not to block, and avoid any UI activity in the callback.
*/
virtual void handleNoteOn (MidiKeyboardState* source,
int midiChannel, int midiNoteNumber, float velocity) = 0;
/** Called when one of the MidiKeyboardState's keys is released.
This will be called synchronously when the state is either processing a
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
when a note is being played with its MidiKeyboardState::noteOff() method.
Note that this callback could happen from an audio callback thread, so be
careful not to block, and avoid any UI activity in the callback.
*/
virtual void handleNoteOff (MidiKeyboardState* source,
int midiChannel, int midiNoteNumber, float velocity) = 0;
};
/** Registers a listener for callbacks when keys go up or down.
@see removeListener
*/
void addListener (Listener* listener);
/** Deregisters a listener.
@see addListener
*/
void removeListener (Listener* listener);
private:
//==============================================================================
CriticalSection lock;
std::atomic<uint16> noteStates[128];
MidiBuffer eventsToAdd;
ListenerList<Listener> listeners;
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity);
void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState)
};
using MidiKeyboardStateListener = MidiKeyboardState::Listener;
} // namespace juce

+ 1144
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.cpp
File diff suppressed because it is too large
View File


+ 952
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h View File

@@ -0,0 +1,952 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Encapsulates a MIDI message.
@see MidiMessageSequence, MidiOutput, MidiInput
@tags{Audio}
*/
class JUCE_API MidiMessage
{
public:
//==============================================================================
/** Creates a 3-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param byte3 message byte 3
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept;
/** Creates a 2-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept;
/** Creates a 1-byte short midi message.
@param byte1 message byte 1
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, double timeStamp = 0) noexcept;
/** Creates a midi message from a list of bytes. */
template <typename... Data>
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes))
{
// this checks that the length matches the data..
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size);
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... };
memcpy (allocateSpace (size), data, (size_t) size);
}
/** Creates a midi message from a block of data. */
MidiMessage (const void* data, int numBytes, double timeStamp = 0);
/** Reads the next midi message from some data.
This will read as many bytes from a data stream as it needs to make a
complete message, and will return the number of bytes it used. This lets
you read a sequence of midi messages from a file or stream.
@param data the data to read from
@param maxBytesToUse the maximum number of bytes it's allowed to read
@param numBytesUsed returns the number of bytes that were actually needed
@param lastStatusByte in a sequence of midi messages, the initial byte
can be dropped from a message if it's the same as the
first byte of the previous message, so this lets you
supply the byte to use if the first byte of the message
has in fact been dropped.
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether
to expect the data to begin with a variable-length
field indicating its size
*/
MidiMessage (const void* data, int maxBytesToUse,
int& numBytesUsed, uint8 lastStatusByte,
double timeStamp = 0,
bool sysexHasEmbeddedLength = true);
/** Creates an active-sense message.
Since the MidiMessage has to contain a valid message, this default constructor
just initialises it with an empty sysex message.
*/
MidiMessage() noexcept;
/** Creates a copy of another midi message. */
MidiMessage (const MidiMessage&);
/** Creates a copy of another midi message, with a different timestamp. */
MidiMessage (const MidiMessage&, double newTimeStamp);
/** Destructor. */
~MidiMessage() noexcept;
/** Copies this message from another one. */
MidiMessage& operator= (const MidiMessage& other);
/** Move constructor */
MidiMessage (MidiMessage&&) noexcept;
/** Move assignment operator */
MidiMessage& operator= (MidiMessage&&) noexcept;
//==============================================================================
/** Returns a pointer to the raw midi data.
@see getRawDataSize
*/
const uint8* getRawData() const noexcept { return getData(); }
/** Returns the number of bytes of data in the message.
@see getRawData
*/
int getRawDataSize() const noexcept { return size; }
//==============================================================================
/** Returns a human-readable description of the midi message as a string,
for example "Note On C#3 Velocity 120 Channel 1".
*/
String getDescription() const;
//==============================================================================
/** Returns the timestamp associated with this message.
The exact meaning of this time and its units will vary, as messages are used in
a variety of different contexts.
If you're getting the message from a midi file, this could be a time in seconds, or
a number of ticks - see MidiFile::convertTimestampTicksToSeconds().
If the message is being used in a MidiBuffer, it might indicate the number of
audio samples from the start of the buffer.
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage()
for details of the way that it initialises this value.
@see setTimeStamp, addToTimeStamp
*/
double getTimeStamp() const noexcept { return timeStamp; }
/** Changes the message's associated timestamp.
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
@see addToTimeStamp, getTimeStamp
*/
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; }
/** Adds a value to the message's timestamp.
The units for the timestamp will be application-specific.
*/
void addToTimeStamp (double delta) noexcept { timeStamp += delta; }
/** Return a copy of this message with a new timestamp.
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
*/
MidiMessage withTimeStamp (double newTimestamp) const;
//==============================================================================
/** Returns the midi channel associated with the message.
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g.
if it's a sysex)
@see isForChannel, setChannel
*/
int getChannel() const noexcept;
/** Returns true if the message applies to the given midi channel.
@param channelNumber the channel number to look for, in the range 1 to 16
@see getChannel, setChannel
*/
bool isForChannel (int channelNumber) const noexcept;
/** Changes the message's midi channel.
This won't do anything for non-channel messages like sysexes.
@param newChannelNumber the channel number to change it to, in the range 1 to 16
*/
void setChannel (int newChannelNumber) noexcept;
//==============================================================================
/** Returns true if this is a system-exclusive message.
*/
bool isSysEx() const noexcept;
/** Returns a pointer to the sysex data inside the message.
If this event isn't a sysex event, it'll return 0.
@see getSysExDataSize
*/
const uint8* getSysExData() const noexcept;
/** Returns the size of the sysex data.
This value excludes the 0xf0 header byte and the 0xf7 at the end.
@see getSysExData
*/
int getSysExDataSize() const noexcept;
//==============================================================================
/** Returns true if this message is a 'key-down' event.
@param returnTrueForVelocity0 if true, then if this event is a note-on with
velocity 0, it will still be considered to be a note-on and the
method will return true. If returnTrueForVelocity0 is false, then
if this is a note-on event with velocity 0, it'll be regarded as
a note-off, and the method will return false
@see isNoteOff, getNoteNumber, getVelocity, noteOn
*/
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept;
/** Creates a key-down message (using a floating-point velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 1.0
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept;
/** Creates a key-down message (using an integer velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept;
/** Returns true if this message is a 'key-up' event.
If returnTrueForNoteOnVelocity0 is true, then his will also return true
for a note-on event with a velocity of 0.
@see isNoteOn, getNoteNumber, getVelocity, noteOff
*/
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 1.0
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber) noexcept;
/** Returns true if this message is a 'key-down' or 'key-up' event.
@see isNoteOn, isNoteOff
*/
bool isNoteOnOrOff() const noexcept;
/** Returns the midi note number for note-on and note-off messages.
If the message isn't a note-on or off, the value returned is undefined.
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber
*/
int getNoteNumber() const noexcept;
/** Changes the midi note number of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
*/
void setNoteNumber (int newNoteNumber) noexcept;
//==============================================================================
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 127.
If the message isn't a note-on or off event, it will return 0.
@see getFloatVelocity
*/
uint8 getVelocity() const noexcept;
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 1.0
If the message isn't a note-on or off event, it will return 0.
@see getVelocity, setVelocity
*/
float getFloatVelocity() const noexcept;
/** Changes the velocity of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
@param newVelocity the new velocity, in the range 0 to 1.0
@see getFloatVelocity, multiplyVelocity
*/
void setVelocity (float newVelocity) noexcept;
/** Multiplies the velocity of a note-on or note-off message by a given amount.
If the message isn't a note on or off, this will do nothing.
@param scaleFactor the value by which to multiply the velocity
@see setVelocity
*/
void multiplyVelocity (float scaleFactor) noexcept;
//==============================================================================
/** Returns true if this message is a 'sustain pedal down' controller message. */
bool isSustainPedalOn() const noexcept;
/** Returns true if this message is a 'sustain pedal up' controller message. */
bool isSustainPedalOff() const noexcept;
/** Returns true if this message is a 'sostenuto pedal down' controller message. */
bool isSostenutoPedalOn() const noexcept;
/** Returns true if this message is a 'sostenuto pedal up' controller message. */
bool isSostenutoPedalOff() const noexcept;
/** Returns true if this message is a 'soft pedal down' controller message. */
bool isSoftPedalOn() const noexcept;
/** Returns true if this message is a 'soft pedal up' controller message. */
bool isSoftPedalOff() const noexcept;
//==============================================================================
/** Returns true if the message is a program (patch) change message.
@see getProgramChangeNumber, getGMInstrumentName
*/
bool isProgramChange() const noexcept;
/** Returns the new program number of a program change message.
If the message isn't a program change, the value returned is undefined.
@see isProgramChange, getGMInstrumentName
*/
int getProgramChangeNumber() const noexcept;
/** Creates a program-change message.
@param channel the midi channel, in the range 1 to 16
@param programNumber the midi program number, 0 to 127
@see isProgramChange, getGMInstrumentName
*/
static MidiMessage programChange (int channel, int programNumber) noexcept;
//==============================================================================
/** Returns true if the message is a pitch-wheel move.
@see getPitchWheelValue, pitchWheel
*/
bool isPitchWheel() const noexcept;
/** Returns the pitch wheel position from a pitch-wheel move message.
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position.
If called for messages which aren't pitch wheel events, the number returned will be
nonsense.
@see isPitchWheel
*/
int getPitchWheelValue() const noexcept;
/** Creates a pitch-wheel move message.
@param channel the midi channel, in the range 1 to 16
@param position the wheel position, in the range 0 to 16383
@see isPitchWheel
*/
static MidiMessage pitchWheel (int channel, int position) noexcept;
//==============================================================================
/** Returns true if the message is an aftertouch event.
For aftertouch events, use the getNoteNumber() method to find out the key
that it applies to, and getAftertouchValue() to find out the amount. Use
getChannel() to find out the channel.
@see getAftertouchValue, getNoteNumber
*/
bool isAftertouch() const noexcept;
/** Returns the amount of aftertouch from an aftertouch messages.
The value returned is in the range 0 to 127, and will be nonsense for messages
other than aftertouch messages.
@see isAftertouch
*/
int getAfterTouchValue() const noexcept;
/** Creates an aftertouch message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param aftertouchAmount the amount of aftertouch, 0 to 127
@see isAftertouch
*/
static MidiMessage aftertouchChange (int channel,
int noteNumber,
int aftertouchAmount) noexcept;
/** Returns true if the message is a channel-pressure change event.
This is like aftertouch, but common to the whole channel rather than a specific
note. Use getChannelPressureValue() to find out the pressure, and getChannel()
to find out the channel.
@see channelPressureChange
*/
bool isChannelPressure() const noexcept;
/** Returns the pressure from a channel pressure change message.
@returns the pressure, in the range 0 to 127
@see isChannelPressure, channelPressureChange
*/
int getChannelPressureValue() const noexcept;
/** Creates a channel-pressure change event.
@param channel the midi channel: 1 to 16
@param pressure the pressure, 0 to 127
@see isChannelPressure
*/
static MidiMessage channelPressureChange (int channel, int pressure) noexcept;
//==============================================================================
/** Returns true if this is a midi controller message.
@see getControllerNumber, getControllerValue, controllerEvent
*/
bool isController() const noexcept;
/** Returns the controller number of a controller message.
The name of the controller can be looked up using the getControllerName() method.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerName, getControllerValue
*/
int getControllerNumber() const noexcept;
/** Returns the controller value from a controller message.
A value 0 to 127 is returned to indicate the new controller position.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerNumber
*/
int getControllerValue() const noexcept;
/** Returns true if this message is a controller message and if it has the specified
controller type.
*/
bool isControllerOfType (int controllerType) const noexcept;
/** Creates a controller message.
@param channel the midi channel, in the range 1 to 16
@param controllerType the type of controller
@param value the controller value
@see isController
*/
static MidiMessage controllerEvent (int channel,
int controllerType,
int value) noexcept;
/** Checks whether this message is an all-notes-off message.
@see allNotesOff
*/
bool isAllNotesOff() const noexcept;
/** Checks whether this message is an all-sound-off message.
@see allSoundOff
*/
bool isAllSoundOff() const noexcept;
/** Checks whether this message is a reset all controllers message.
@see allControllerOff
*/
bool isResetAllControllers() const noexcept;
/** Creates an all-notes-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllNotesOff
*/
static MidiMessage allNotesOff (int channel) noexcept;
/** Creates an all-sound-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllSoundOff
*/
static MidiMessage allSoundOff (int channel) noexcept;
/** Creates an all-controllers-off message.
@param channel the midi channel, in the range 1 to 16
*/
static MidiMessage allControllersOff (int channel) noexcept;
//==============================================================================
/** Returns true if this event is a meta-event.
Meta-events are things like tempo changes, track names, etc.
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
bool isMetaEvent() const noexcept;
/** Returns a meta-event's type number.
If the message isn't a meta-event, this will return -1.
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
int getMetaEventType() const noexcept;
/** Returns a pointer to the data in a meta-event.
@see isMetaEvent, getMetaEventLength
*/
const uint8* getMetaEventData() const noexcept;
/** Returns the length of the data for a meta-event.
@see isMetaEvent, getMetaEventData
*/
int getMetaEventLength() const noexcept;
//==============================================================================
/** Returns true if this is a 'track' meta-event. */
bool isTrackMetaEvent() const noexcept;
/** Returns true if this is an 'end-of-track' meta-event. */
bool isEndOfTrackMetaEvent() const noexcept;
/** Creates an end-of-track meta-event.
@see isEndOfTrackMetaEvent
*/
static MidiMessage endOfTrack() noexcept;
/** Returns true if this is an 'track name' meta-event.
You can use the getTextFromTextMetaEvent() method to get the track's name.
*/
bool isTrackNameEvent() const noexcept;
/** Returns true if this is a 'text' meta-event.
@see getTextFromTextMetaEvent
*/
bool isTextMetaEvent() const noexcept;
/** Returns the text from a text meta-event.
@see isTextMetaEvent
*/
String getTextFromTextMetaEvent() const;
/** Creates a text meta-event. */
static MidiMessage textMetaEvent (int type, StringRef text);
//==============================================================================
/** Returns true if this is a 'tempo' meta-event.
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote
*/
bool isTempoMetaEvent() const noexcept;
/** Returns the tick length from a tempo meta-event.
@param timeFormat the 16-bit time format value from the midi file's header.
@returns the tick length (in seconds).
@see isTempoMetaEvent
*/
double getTempoMetaEventTickLength (short timeFormat) const noexcept;
/** Calculates the seconds-per-quarter-note from a tempo meta-event.
@see isTempoMetaEvent, getTempoMetaEventTickLength
*/
double getTempoSecondsPerQuarterNote() const noexcept;
/** Creates a tempo meta-event.
@see isTempoMetaEvent
*/
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept;
//==============================================================================
/** Returns true if this is a 'time-signature' meta-event.
@see getTimeSignatureInfo
*/
bool isTimeSignatureMetaEvent() const noexcept;
/** Returns the time-signature values from a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept;
/** Creates a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator);
//==============================================================================
/** Returns true if this is a 'key-signature' meta-event.
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey
*/
bool isKeySignatureMetaEvent() const noexcept;
/** Returns the key from a key-signature meta-event.
This method must only be called if isKeySignatureMetaEvent() is true.
A positive number here indicates the number of sharps in the key signature,
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#,
-2 = Bb + Eb
@see isKeySignatureMetaEvent, isKeySignatureMajorKey
*/
int getKeySignatureNumberOfSharpsOrFlats() const noexcept;
/** Returns true if this key-signature event is major, or false if it's minor.
This method must only be called if isKeySignatureMetaEvent() is true.
*/
bool isKeySignatureMajorKey() const noexcept;
/** Creates a key-signature meta-event.
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps
in the key; if negative, the number of flats
@param isMinorKey if true, the key is minor; if false, it is major
@see isKeySignatureMetaEvent
*/
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey);
//==============================================================================
/** Returns true if this is a 'channel' meta-event.
A channel meta-event specifies the midi channel that should be used
for subsequent meta-events.
@see getMidiChannelMetaEventChannel
*/
bool isMidiChannelMetaEvent() const noexcept;
/** Returns the channel number from a channel meta-event.
@returns the channel, in the range 1 to 16.
@see isMidiChannelMetaEvent
*/
int getMidiChannelMetaEventChannel() const noexcept;
/** Creates a midi channel meta-event.
@param channel the midi channel, in the range 1 to 16
@see isMidiChannelMetaEvent
*/
static MidiMessage midiChannelMetaEvent (int channel) noexcept;
//==============================================================================
/** Returns true if this is an active-sense message. */
bool isActiveSense() const noexcept;
//==============================================================================
/** Returns true if this is a midi start event.
@see midiStart
*/
bool isMidiStart() const noexcept;
/** Creates a midi start event. */
static MidiMessage midiStart() noexcept;
/** Returns true if this is a midi continue event.
@see midiContinue
*/
bool isMidiContinue() const noexcept;
/** Creates a midi continue event. */
static MidiMessage midiContinue() noexcept;
/** Returns true if this is a midi stop event.
@see midiStop
*/
bool isMidiStop() const noexcept;
/** Creates a midi stop event. */
static MidiMessage midiStop() noexcept;
/** Returns true if this is a midi clock event.
@see midiClock, songPositionPointer
*/
bool isMidiClock() const noexcept;
/** Creates a midi clock event. */
static MidiMessage midiClock() noexcept;
/** Returns true if this is a song-position-pointer message.
@see getSongPositionPointerMidiBeat, songPositionPointer
*/
bool isSongPositionPointer() const noexcept;
/** Returns the midi beat-number of a song-position-pointer message.
@see isSongPositionPointer, songPositionPointer
*/
int getSongPositionPointerMidiBeat() const noexcept;
/** Creates a song-position-pointer message.
The position is a number of midi beats from the start of the song, where 1 midi
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there
are 4 midi beats in a quarter-note.
@see isSongPositionPointer, getSongPositionPointerMidiBeat
*/
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept;
//==============================================================================
/** Returns true if this is a quarter-frame midi timecode message.
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue
*/
bool isQuarterFrame() const noexcept;
/** Returns the sequence number of a quarter-frame midi timecode message.
This will be a value between 0 and 7.
@see isQuarterFrame, getQuarterFrameValue, quarterFrame
*/
int getQuarterFrameSequenceNumber() const noexcept;
/** Returns the value from a quarter-frame message.
This will be the lower nybble of the message's data-byte, a value between 0 and 15
*/
int getQuarterFrameValue() const noexcept;
/** Creates a quarter-frame MTC message.
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte
@param value a value 0 to 15 for the lower nybble of the message's data byte
*/
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept;
/** SMPTE timecode types.
Used by the getFullFrameParameters() and fullFrame() methods.
*/
enum SmpteTimecodeType
{
fps24 = 0,
fps25 = 1,
fps30drop = 2,
fps30 = 3
};
/** Returns true if this is a full-frame midi timecode message. */
bool isFullFrame() const noexcept;
/** Extracts the timecode information from a full-frame midi timecode message.
You should only call this on messages where you've used isFullFrame() to
check that they're the right kind.
*/
void getFullFrameParameters (int& hours,
int& minutes,
int& seconds,
int& frames,
SmpteTimecodeType& timecodeType) const noexcept;
/** Creates a full-frame MTC message. */
static MidiMessage fullFrame (int hours,
int minutes,
int seconds,
int frames,
SmpteTimecodeType timecodeType);
//==============================================================================
/** Types of MMC command.
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand
*/
enum MidiMachineControlCommand
{
mmc_stop = 1,
mmc_play = 2,
mmc_deferredplay = 3,
mmc_fastforward = 4,
mmc_rewind = 5,
mmc_recordStart = 6,
mmc_recordStop = 7,
mmc_pause = 9
};
/** Checks whether this is an MMC message.
If it is, you can use the getMidiMachineControlCommand() to find out its type.
*/
bool isMidiMachineControlMessage() const noexcept;
/** For an MMC message, this returns its type.
Make sure it's actually an MMC message with isMidiMachineControlMessage() before
calling this method.
*/
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept;
/** Creates an MMC message. */
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command);
/** Checks whether this is an MMC "goto" message.
If it is, the parameters passed-in are set to the time that the message contains.
@see midiMachineControlGoto
*/
bool isMidiMachineControlGoto (int& hours,
int& minutes,
int& seconds,
int& frames) const noexcept;
/** Creates an MMC "goto" message.
This messages tells the device to go to a specific frame.
@see isMidiMachineControlGoto
*/
static MidiMessage midiMachineControlGoto (int hours,
int minutes,
int seconds,
int frames);
//==============================================================================
/** Creates a master-volume change message.
@param volume the volume, 0 to 1.0
*/
static MidiMessage masterVolume (float volume);
//==============================================================================
/** Creates a system-exclusive message.
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
*/
static MidiMessage createSysExMessage (const void* sysexData,
int dataSize);
//==============================================================================
/** Reads a midi variable-length integer.
@param data the data to read the number from
@param numBytesUsed on return, this will be set to the number of bytes that were read
*/
static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept;
/** Based on the first byte of a short midi message, this uses a lookup table
to return the message length (either 1, 2, or 3 bytes).
The value passed in must be 0x80 or higher.
*/
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept;
//==============================================================================
/** Returns the name of a midi note number.
E.g "C", "D#", etc.
@param noteNumber the midi note number, 0 to 127
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise
they'll be flattened, e.g. "Db"
@param includeOctaveNumber if true, the octave number will be appended to the string,
e.g. "C#4"
@param octaveNumForMiddleC if an octave number is being appended, this indicates the
number that will be used for middle C's octave
@see getMidiNoteInHertz
*/
static String getMidiNoteName (int noteNumber,
bool useSharps,
bool includeOctaveNumber,
int octaveNumForMiddleC);
/** Returns the frequency of a midi note number.
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch.
@see getMidiNoteName
*/
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept;
/** Returns true if the given midi note number is a black key. */
static bool isMidiNoteBlack (int noteNumber) noexcept;
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index.
@param midiInstrumentNumber the program number 0 to 127
@see getProgramChangeNumber
*/
static const char* getGMInstrumentName (int midiInstrumentNumber);
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number.
@param midiBankNumber the bank, 0 to 15
*/
static const char* getGMInstrumentBankName (int midiBankNumber);
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number.
@param midiNoteNumber the key number, 35 to 81
*/
static const char* getRhythmInstrumentName (int midiNoteNumber);
/** Returns the name of a controller type number, or nullptr if unknown for this controller number.
@see getControllerNumber
*/
static const char* getControllerName (int controllerNumber);
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept;
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones,
float pitchbendRangeInSemitones) noexcept;
private:
//==============================================================================
#ifndef DOXYGEN
union PackedData
{
uint8* allocatedData;
uint8 asBytes[sizeof (uint8*)];
};
PackedData packedData;
double timeStamp = 0;
int size;
#endif
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); }
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : const_cast<uint8*>(packedData.asBytes); }
uint8* allocateSpace (int);
};
} // namespace juce

+ 952
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h.orig View File

@@ -0,0 +1,952 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Encapsulates a MIDI message.
@see MidiMessageSequence, MidiOutput, MidiInput
@tags{Audio}
*/
class JUCE_API MidiMessage
{
public:
//==============================================================================
/** Creates a 3-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param byte3 message byte 3
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept;
/** Creates a 2-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept;
/** Creates a 1-byte short midi message.
@param byte1 message byte 1
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, double timeStamp = 0) noexcept;
/** Creates a midi message from a list of bytes. */
template <typename... Data>
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes))
{
// this checks that the length matches the data..
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size);
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... };
memcpy (allocateSpace (size), data, (size_t) size);
}
/** Creates a midi message from a block of data. */
MidiMessage (const void* data, int numBytes, double timeStamp = 0);
/** Reads the next midi message from some data.
This will read as many bytes from a data stream as it needs to make a
complete message, and will return the number of bytes it used. This lets
you read a sequence of midi messages from a file or stream.
@param data the data to read from
@param maxBytesToUse the maximum number of bytes it's allowed to read
@param numBytesUsed returns the number of bytes that were actually needed
@param lastStatusByte in a sequence of midi messages, the initial byte
can be dropped from a message if it's the same as the
first byte of the previous message, so this lets you
supply the byte to use if the first byte of the message
has in fact been dropped.
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether
to expect the data to begin with a variable-length
field indicating its size
*/
MidiMessage (const void* data, int maxBytesToUse,
int& numBytesUsed, uint8 lastStatusByte,
double timeStamp = 0,
bool sysexHasEmbeddedLength = true);
/** Creates an active-sense message.
Since the MidiMessage has to contain a valid message, this default constructor
just initialises it with an empty sysex message.
*/
MidiMessage() noexcept;
/** Creates a copy of another midi message. */
MidiMessage (const MidiMessage&);
/** Creates a copy of another midi message, with a different timestamp. */
MidiMessage (const MidiMessage&, double newTimeStamp);
/** Destructor. */
~MidiMessage() noexcept;
/** Copies this message from another one. */
MidiMessage& operator= (const MidiMessage& other);
/** Move constructor */
MidiMessage (MidiMessage&&) noexcept;
/** Move assignment operator */
MidiMessage& operator= (MidiMessage&&) noexcept;
//==============================================================================
/** Returns a pointer to the raw midi data.
@see getRawDataSize
*/
const uint8* getRawData() const noexcept { return getData(); }
/** Returns the number of bytes of data in the message.
@see getRawData
*/
int getRawDataSize() const noexcept { return size; }
//==============================================================================
/** Returns a human-readable description of the midi message as a string,
for example "Note On C#3 Velocity 120 Channel 1".
*/
String getDescription() const;
//==============================================================================
/** Returns the timestamp associated with this message.
The exact meaning of this time and its units will vary, as messages are used in
a variety of different contexts.
If you're getting the message from a midi file, this could be a time in seconds, or
a number of ticks - see MidiFile::convertTimestampTicksToSeconds().
If the message is being used in a MidiBuffer, it might indicate the number of
audio samples from the start of the buffer.
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage()
for details of the way that it initialises this value.
@see setTimeStamp, addToTimeStamp
*/
double getTimeStamp() const noexcept { return timeStamp; }
/** Changes the message's associated timestamp.
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
@see addToTimeStamp, getTimeStamp
*/
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; }
/** Adds a value to the message's timestamp.
The units for the timestamp will be application-specific.
*/
void addToTimeStamp (double delta) noexcept { timeStamp += delta; }
/** Return a copy of this message with a new timestamp.
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
*/
MidiMessage withTimeStamp (double newTimestamp) const;
//==============================================================================
/** Returns the midi channel associated with the message.
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g.
if it's a sysex)
@see isForChannel, setChannel
*/
int getChannel() const noexcept;
/** Returns true if the message applies to the given midi channel.
@param channelNumber the channel number to look for, in the range 1 to 16
@see getChannel, setChannel
*/
bool isForChannel (int channelNumber) const noexcept;
/** Changes the message's midi channel.
This won't do anything for non-channel messages like sysexes.
@param newChannelNumber the channel number to change it to, in the range 1 to 16
*/
void setChannel (int newChannelNumber) noexcept;
//==============================================================================
/** Returns true if this is a system-exclusive message.
*/
bool isSysEx() const noexcept;
/** Returns a pointer to the sysex data inside the message.
If this event isn't a sysex event, it'll return 0.
@see getSysExDataSize
*/
const uint8* getSysExData() const noexcept;
/** Returns the size of the sysex data.
This value excludes the 0xf0 header byte and the 0xf7 at the end.
@see getSysExData
*/
int getSysExDataSize() const noexcept;
//==============================================================================
/** Returns true if this message is a 'key-down' event.
@param returnTrueForVelocity0 if true, then if this event is a note-on with
velocity 0, it will still be considered to be a note-on and the
method will return true. If returnTrueForVelocity0 is false, then
if this is a note-on event with velocity 0, it'll be regarded as
a note-off, and the method will return false
@see isNoteOff, getNoteNumber, getVelocity, noteOn
*/
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept;
/** Creates a key-down message (using a floating-point velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 1.0
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept;
/** Creates a key-down message (using an integer velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept;
/** Returns true if this message is a 'key-up' event.
If returnTrueForNoteOnVelocity0 is true, then his will also return true
for a note-on event with a velocity of 0.
@see isNoteOn, getNoteNumber, getVelocity, noteOff
*/
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 1.0
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber) noexcept;
/** Returns true if this message is a 'key-down' or 'key-up' event.
@see isNoteOn, isNoteOff
*/
bool isNoteOnOrOff() const noexcept;
/** Returns the midi note number for note-on and note-off messages.
If the message isn't a note-on or off, the value returned is undefined.
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber
*/
int getNoteNumber() const noexcept;
/** Changes the midi note number of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
*/
void setNoteNumber (int newNoteNumber) noexcept;
//==============================================================================
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 127.
If the message isn't a note-on or off event, it will return 0.
@see getFloatVelocity
*/
uint8 getVelocity() const noexcept;
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 1.0
If the message isn't a note-on or off event, it will return 0.
@see getVelocity, setVelocity
*/
float getFloatVelocity() const noexcept;
/** Changes the velocity of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
@param newVelocity the new velocity, in the range 0 to 1.0
@see getFloatVelocity, multiplyVelocity
*/
void setVelocity (float newVelocity) noexcept;
/** Multiplies the velocity of a note-on or note-off message by a given amount.
If the message isn't a note on or off, this will do nothing.
@param scaleFactor the value by which to multiply the velocity
@see setVelocity
*/
void multiplyVelocity (float scaleFactor) noexcept;
//==============================================================================
/** Returns true if this message is a 'sustain pedal down' controller message. */
bool isSustainPedalOn() const noexcept;
/** Returns true if this message is a 'sustain pedal up' controller message. */
bool isSustainPedalOff() const noexcept;
/** Returns true if this message is a 'sostenuto pedal down' controller message. */
bool isSostenutoPedalOn() const noexcept;
/** Returns true if this message is a 'sostenuto pedal up' controller message. */
bool isSostenutoPedalOff() const noexcept;
/** Returns true if this message is a 'soft pedal down' controller message. */
bool isSoftPedalOn() const noexcept;
/** Returns true if this message is a 'soft pedal up' controller message. */
bool isSoftPedalOff() const noexcept;
//==============================================================================
/** Returns true if the message is a program (patch) change message.
@see getProgramChangeNumber, getGMInstrumentName
*/
bool isProgramChange() const noexcept;
/** Returns the new program number of a program change message.
If the message isn't a program change, the value returned is undefined.
@see isProgramChange, getGMInstrumentName
*/
int getProgramChangeNumber() const noexcept;
/** Creates a program-change message.
@param channel the midi channel, in the range 1 to 16
@param programNumber the midi program number, 0 to 127
@see isProgramChange, getGMInstrumentName
*/
static MidiMessage programChange (int channel, int programNumber) noexcept;
//==============================================================================
/** Returns true if the message is a pitch-wheel move.
@see getPitchWheelValue, pitchWheel
*/
bool isPitchWheel() const noexcept;
/** Returns the pitch wheel position from a pitch-wheel move message.
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position.
If called for messages which aren't pitch wheel events, the number returned will be
nonsense.
@see isPitchWheel
*/
int getPitchWheelValue() const noexcept;
/** Creates a pitch-wheel move message.
@param channel the midi channel, in the range 1 to 16
@param position the wheel position, in the range 0 to 16383
@see isPitchWheel
*/
static MidiMessage pitchWheel (int channel, int position) noexcept;
//==============================================================================
/** Returns true if the message is an aftertouch event.
For aftertouch events, use the getNoteNumber() method to find out the key
that it applies to, and getAftertouchValue() to find out the amount. Use
getChannel() to find out the channel.
@see getAftertouchValue, getNoteNumber
*/
bool isAftertouch() const noexcept;
/** Returns the amount of aftertouch from an aftertouch messages.
The value returned is in the range 0 to 127, and will be nonsense for messages
other than aftertouch messages.
@see isAftertouch
*/
int getAfterTouchValue() const noexcept;
/** Creates an aftertouch message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param aftertouchAmount the amount of aftertouch, 0 to 127
@see isAftertouch
*/
static MidiMessage aftertouchChange (int channel,
int noteNumber,
int aftertouchAmount) noexcept;
/** Returns true if the message is a channel-pressure change event.
This is like aftertouch, but common to the whole channel rather than a specific
note. Use getChannelPressureValue() to find out the pressure, and getChannel()
to find out the channel.
@see channelPressureChange
*/
bool isChannelPressure() const noexcept;
/** Returns the pressure from a channel pressure change message.
@returns the pressure, in the range 0 to 127
@see isChannelPressure, channelPressureChange
*/
int getChannelPressureValue() const noexcept;
/** Creates a channel-pressure change event.
@param channel the midi channel: 1 to 16
@param pressure the pressure, 0 to 127
@see isChannelPressure
*/
static MidiMessage channelPressureChange (int channel, int pressure) noexcept;
//==============================================================================
/** Returns true if this is a midi controller message.
@see getControllerNumber, getControllerValue, controllerEvent
*/
bool isController() const noexcept;
/** Returns the controller number of a controller message.
The name of the controller can be looked up using the getControllerName() method.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerName, getControllerValue
*/
int getControllerNumber() const noexcept;
/** Returns the controller value from a controller message.
A value 0 to 127 is returned to indicate the new controller position.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerNumber
*/
int getControllerValue() const noexcept;
/** Returns true if this message is a controller message and if it has the specified
controller type.
*/
bool isControllerOfType (int controllerType) const noexcept;
/** Creates a controller message.
@param channel the midi channel, in the range 1 to 16
@param controllerType the type of controller
@param value the controller value
@see isController
*/
static MidiMessage controllerEvent (int channel,
int controllerType,
int value) noexcept;
/** Checks whether this message is an all-notes-off message.
@see allNotesOff
*/
bool isAllNotesOff() const noexcept;
/** Checks whether this message is an all-sound-off message.
@see allSoundOff
*/
bool isAllSoundOff() const noexcept;
/** Checks whether this message is a reset all controllers message.
@see allControllerOff
*/
bool isResetAllControllers() const noexcept;
/** Creates an all-notes-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllNotesOff
*/
static MidiMessage allNotesOff (int channel) noexcept;
/** Creates an all-sound-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllSoundOff
*/
static MidiMessage allSoundOff (int channel) noexcept;
/** Creates an all-controllers-off message.
@param channel the midi channel, in the range 1 to 16
*/
static MidiMessage allControllersOff (int channel) noexcept;
//==============================================================================
/** Returns true if this event is a meta-event.
Meta-events are things like tempo changes, track names, etc.
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
bool isMetaEvent() const noexcept;
/** Returns a meta-event's type number.
If the message isn't a meta-event, this will return -1.
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
int getMetaEventType() const noexcept;
/** Returns a pointer to the data in a meta-event.
@see isMetaEvent, getMetaEventLength
*/
const uint8* getMetaEventData() const noexcept;
/** Returns the length of the data for a meta-event.
@see isMetaEvent, getMetaEventData
*/
int getMetaEventLength() const noexcept;
//==============================================================================
/** Returns true if this is a 'track' meta-event. */
bool isTrackMetaEvent() const noexcept;
/** Returns true if this is an 'end-of-track' meta-event. */
bool isEndOfTrackMetaEvent() const noexcept;
/** Creates an end-of-track meta-event.
@see isEndOfTrackMetaEvent
*/
static MidiMessage endOfTrack() noexcept;
/** Returns true if this is an 'track name' meta-event.
You can use the getTextFromTextMetaEvent() method to get the track's name.
*/
bool isTrackNameEvent() const noexcept;
/** Returns true if this is a 'text' meta-event.
@see getTextFromTextMetaEvent
*/
bool isTextMetaEvent() const noexcept;
/** Returns the text from a text meta-event.
@see isTextMetaEvent
*/
String getTextFromTextMetaEvent() const;
/** Creates a text meta-event. */
static MidiMessage textMetaEvent (int type, StringRef text);
//==============================================================================
/** Returns true if this is a 'tempo' meta-event.
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote
*/
bool isTempoMetaEvent() const noexcept;
/** Returns the tick length from a tempo meta-event.
@param timeFormat the 16-bit time format value from the midi file's header.
@returns the tick length (in seconds).
@see isTempoMetaEvent
*/
double getTempoMetaEventTickLength (short timeFormat) const noexcept;
/** Calculates the seconds-per-quarter-note from a tempo meta-event.
@see isTempoMetaEvent, getTempoMetaEventTickLength
*/
double getTempoSecondsPerQuarterNote() const noexcept;
/** Creates a tempo meta-event.
@see isTempoMetaEvent
*/
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept;
//==============================================================================
/** Returns true if this is a 'time-signature' meta-event.
@see getTimeSignatureInfo
*/
bool isTimeSignatureMetaEvent() const noexcept;
/** Returns the time-signature values from a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept;
/** Creates a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator);
//==============================================================================
/** Returns true if this is a 'key-signature' meta-event.
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey
*/
bool isKeySignatureMetaEvent() const noexcept;
/** Returns the key from a key-signature meta-event.
This method must only be called if isKeySignatureMetaEvent() is true.
A positive number here indicates the number of sharps in the key signature,
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#,
-2 = Bb + Eb
@see isKeySignatureMetaEvent, isKeySignatureMajorKey
*/
int getKeySignatureNumberOfSharpsOrFlats() const noexcept;
/** Returns true if this key-signature event is major, or false if it's minor.
This method must only be called if isKeySignatureMetaEvent() is true.
*/
bool isKeySignatureMajorKey() const noexcept;
/** Creates a key-signature meta-event.
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps
in the key; if negative, the number of flats
@param isMinorKey if true, the key is minor; if false, it is major
@see isKeySignatureMetaEvent
*/
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey);
//==============================================================================
/** Returns true if this is a 'channel' meta-event.
A channel meta-event specifies the midi channel that should be used
for subsequent meta-events.
@see getMidiChannelMetaEventChannel
*/
bool isMidiChannelMetaEvent() const noexcept;
/** Returns the channel number from a channel meta-event.
@returns the channel, in the range 1 to 16.
@see isMidiChannelMetaEvent
*/
int getMidiChannelMetaEventChannel() const noexcept;
/** Creates a midi channel meta-event.
@param channel the midi channel, in the range 1 to 16
@see isMidiChannelMetaEvent
*/
static MidiMessage midiChannelMetaEvent (int channel) noexcept;
//==============================================================================
/** Returns true if this is an active-sense message. */
bool isActiveSense() const noexcept;
//==============================================================================
/** Returns true if this is a midi start event.
@see midiStart
*/
bool isMidiStart() const noexcept;
/** Creates a midi start event. */
static MidiMessage midiStart() noexcept;
/** Returns true if this is a midi continue event.
@see midiContinue
*/
bool isMidiContinue() const noexcept;
/** Creates a midi continue event. */
static MidiMessage midiContinue() noexcept;
/** Returns true if this is a midi stop event.
@see midiStop
*/
bool isMidiStop() const noexcept;
/** Creates a midi stop event. */
static MidiMessage midiStop() noexcept;
/** Returns true if this is a midi clock event.
@see midiClock, songPositionPointer
*/
bool isMidiClock() const noexcept;
/** Creates a midi clock event. */
static MidiMessage midiClock() noexcept;
/** Returns true if this is a song-position-pointer message.
@see getSongPositionPointerMidiBeat, songPositionPointer
*/
bool isSongPositionPointer() const noexcept;
/** Returns the midi beat-number of a song-position-pointer message.
@see isSongPositionPointer, songPositionPointer
*/
int getSongPositionPointerMidiBeat() const noexcept;
/** Creates a song-position-pointer message.
The position is a number of midi beats from the start of the song, where 1 midi
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there
are 4 midi beats in a quarter-note.
@see isSongPositionPointer, getSongPositionPointerMidiBeat
*/
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept;
//==============================================================================
/** Returns true if this is a quarter-frame midi timecode message.
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue
*/
bool isQuarterFrame() const noexcept;
/** Returns the sequence number of a quarter-frame midi timecode message.
This will be a value between 0 and 7.
@see isQuarterFrame, getQuarterFrameValue, quarterFrame
*/
int getQuarterFrameSequenceNumber() const noexcept;
/** Returns the value from a quarter-frame message.
This will be the lower nybble of the message's data-byte, a value between 0 and 15
*/
int getQuarterFrameValue() const noexcept;
/** Creates a quarter-frame MTC message.
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte
@param value a value 0 to 15 for the lower nybble of the message's data byte
*/
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept;
/** SMPTE timecode types.
Used by the getFullFrameParameters() and fullFrame() methods.
*/
enum SmpteTimecodeType
{
fps24 = 0,
fps25 = 1,
fps30drop = 2,
fps30 = 3
};
/** Returns true if this is a full-frame midi timecode message. */
bool isFullFrame() const noexcept;
/** Extracts the timecode information from a full-frame midi timecode message.
You should only call this on messages where you've used isFullFrame() to
check that they're the right kind.
*/
void getFullFrameParameters (int& hours,
int& minutes,
int& seconds,
int& frames,
SmpteTimecodeType& timecodeType) const noexcept;
/** Creates a full-frame MTC message. */
static MidiMessage fullFrame (int hours,
int minutes,
int seconds,
int frames,
SmpteTimecodeType timecodeType);
//==============================================================================
/** Types of MMC command.
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand
*/
enum MidiMachineControlCommand
{
mmc_stop = 1,
mmc_play = 2,
mmc_deferredplay = 3,
mmc_fastforward = 4,
mmc_rewind = 5,
mmc_recordStart = 6,
mmc_recordStop = 7,
mmc_pause = 9
};
/** Checks whether this is an MMC message.
If it is, you can use the getMidiMachineControlCommand() to find out its type.
*/
bool isMidiMachineControlMessage() const noexcept;
/** For an MMC message, this returns its type.
Make sure it's actually an MMC message with isMidiMachineControlMessage() before
calling this method.
*/
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept;
/** Creates an MMC message. */
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command);
/** Checks whether this is an MMC "goto" message.
If it is, the parameters passed-in are set to the time that the message contains.
@see midiMachineControlGoto
*/
bool isMidiMachineControlGoto (int& hours,
int& minutes,
int& seconds,
int& frames) const noexcept;
/** Creates an MMC "goto" message.
This messages tells the device to go to a specific frame.
@see isMidiMachineControlGoto
*/
static MidiMessage midiMachineControlGoto (int hours,
int minutes,
int seconds,
int frames);
//==============================================================================
/** Creates a master-volume change message.
@param volume the volume, 0 to 1.0
*/
static MidiMessage masterVolume (float volume);
//==============================================================================
/** Creates a system-exclusive message.
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
*/
static MidiMessage createSysExMessage (const void* sysexData,
int dataSize);
//==============================================================================
/** Reads a midi variable-length integer.
@param data the data to read the number from
@param numBytesUsed on return, this will be set to the number of bytes that were read
*/
static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept;
/** Based on the first byte of a short midi message, this uses a lookup table
to return the message length (either 1, 2, or 3 bytes).
The value passed in must be 0x80 or higher.
*/
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept;
//==============================================================================
/** Returns the name of a midi note number.
E.g "C", "D#", etc.
@param noteNumber the midi note number, 0 to 127
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise
they'll be flattened, e.g. "Db"
@param includeOctaveNumber if true, the octave number will be appended to the string,
e.g. "C#4"
@param octaveNumForMiddleC if an octave number is being appended, this indicates the
number that will be used for middle C's octave
@see getMidiNoteInHertz
*/
static String getMidiNoteName (int noteNumber,
bool useSharps,
bool includeOctaveNumber,
int octaveNumForMiddleC);
/** Returns the frequency of a midi note number.
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch.
@see getMidiNoteName
*/
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept;
/** Returns true if the given midi note number is a black key. */
static bool isMidiNoteBlack (int noteNumber) noexcept;
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index.
@param midiInstrumentNumber the program number 0 to 127
@see getProgramChangeNumber
*/
static const char* getGMInstrumentName (int midiInstrumentNumber);
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number.
@param midiBankNumber the bank, 0 to 15
*/
static const char* getGMInstrumentBankName (int midiBankNumber);
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number.
@param midiNoteNumber the key number, 35 to 81
*/
static const char* getRhythmInstrumentName (int midiNoteNumber);
/** Returns the name of a controller type number, or nullptr if unknown for this controller number.
@see getControllerNumber
*/
static const char* getControllerName (int controllerNumber);
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept;
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones,
float pitchbendRangeInSemitones) noexcept;
private:
//==============================================================================
#ifndef DOXYGEN
union PackedData
{
uint8* allocatedData;
uint8 asBytes[sizeof (uint8*)];
};
PackedData packedData;
double timeStamp = 0;
int size;
#endif
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); }
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; }
uint8* allocateSpace (int);
};
} // namespace juce

+ 11
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessage.h.rej View File

@@ -0,0 +1,11 @@
--- modules/juce_audio_basics/midi/juce_MidiMessage.h
+++ modules/juce_audio_basics/midi/juce_MidiMessage.h
@@ -945,7 +945,7 @@ private:
#endif
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); }
- inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; }
+ inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : const_cast<uint8*>(packedData.asBytes); }
uint8* allocateSpace (int);
};

+ 412
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessageSequence.cpp View File

@@ -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

+ 306
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiMessageSequence.h View File

@@ -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

+ 380
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiRPN.cpp View File

@@ -0,0 +1,380 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MidiRPNDetector::MidiRPNDetector() noexcept
{
}
MidiRPNDetector::~MidiRPNDetector() noexcept
{
}
bool MidiRPNDetector::parseControllerMessage (int midiChannel,
int controllerNumber,
int controllerValue,
MidiRPNMessage& result) noexcept
{
jassert (midiChannel >= 1 && midiChannel <= 16);
jassert (controllerNumber >= 0 && controllerNumber < 128);
jassert (controllerValue >= 0 && controllerValue < 128);
return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result);
}
void MidiRPNDetector::reset() noexcept
{
for (int i = 0; i < 16; ++i)
{
states[i].parameterMSB = 0xff;
states[i].parameterLSB = 0xff;
states[i].resetValue();
states[i].isNRPN = false;
}
}
//==============================================================================
MidiRPNDetector::ChannelState::ChannelState() noexcept
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false)
{
}
bool MidiRPNDetector::ChannelState::handleController (int channel,
int controllerNumber,
int value,
MidiRPNMessage& result) noexcept
{
switch (controllerNumber)
{
case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break;
case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break;
case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break;
case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break;
case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result);
case 0x26: valueLSB = uint8 (value); break;
default: break;
}
return false;
}
void MidiRPNDetector::ChannelState::resetValue() noexcept
{
valueMSB = 0xff;
valueLSB = 0xff;
}
//==============================================================================
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept
{
if (parameterMSB < 0x80 && parameterLSB < 0x80)
{
if (valueMSB < 0x80)
{
result.channel = channel;
result.parameterNumber = (parameterMSB << 7) + parameterLSB;
result.isNRPN = isNRPN;
if (valueLSB < 0x80)
{
result.value = (valueMSB << 7) + valueLSB;
result.is14BitValue = true;
}
else
{
result.value = valueMSB;
result.is14BitValue = false;
}
return true;
}
}
return false;
}
//==============================================================================
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message)
{
return generate (message.channel,
message.parameterNumber,
message.value,
message.isNRPN,
message.is14BitValue);
}
MidiBuffer MidiRPNGenerator::generate (int midiChannel,
int parameterNumber,
int value,
bool isNRPN,
bool use14BitValue)
{
jassert (midiChannel > 0 && midiChannel <= 16);
jassert (parameterNumber >= 0 && parameterNumber < 16384);
jassert (value >= 0 && value < (use14BitValue ? 16384 : 128));
uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f);
uint8 parameterMSB = uint8 (parameterNumber >> 7);
uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00;
uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value);
uint8 channelByte = uint8 (0xb0 + midiChannel - 1);
MidiBuffer buffer;
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0);
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0);
// sending the value LSB is optional, but must come before sending the value MSB:
if (use14BitValue)
buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0);
buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0);
return buffer;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class MidiRPNDetectorTests : public UnitTest
{
public:
MidiRPNDetectorTests()
: UnitTest ("MidiRPNDetector class", UnitTestCategories::midi)
{}
void runTest() override
{
beginTest ("7-bit RPN");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
expect (detector.parseControllerMessage (2, 6, 42, rpn));
expectEquals (rpn.channel, 2);
expectEquals (rpn.parameterNumber, 7);
expectEquals (rpn.value, 42);
expect (! rpn.isNRPN);
expect (! rpn.is14BitValue);
}
beginTest ("14-bit RPN");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (1, 100, 44, rpn));
expect (! detector.parseControllerMessage (1, 101, 2, rpn));
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
expect (detector.parseControllerMessage (1, 6, 1, rpn));
expectEquals (rpn.channel, 1);
expectEquals (rpn.parameterNumber, 300);
expectEquals (rpn.value, 222);
expect (! rpn.isNRPN);
expect (rpn.is14BitValue);
}
beginTest ("RPNs on multiple channels simultaneously");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (1, 100, 44, rpn));
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
expect (! detector.parseControllerMessage (1, 101, 2, rpn));
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
expect (detector.parseControllerMessage (2, 6, 42, rpn));
expectEquals (rpn.channel, 2);
expectEquals (rpn.parameterNumber, 7);
expectEquals (rpn.value, 42);
expect (! rpn.isNRPN);
expect (! rpn.is14BitValue);
expect (detector.parseControllerMessage (1, 6, 1, rpn));
expectEquals (rpn.channel, 1);
expectEquals (rpn.parameterNumber, 300);
expectEquals (rpn.value, 222);
expect (! rpn.isNRPN);
expect (rpn.is14BitValue);
}
beginTest ("14-bit RPN with value within 7-bit range");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
expect (! detector.parseControllerMessage (16, 101, 0, rpn));
expect (! detector.parseControllerMessage (16, 38, 3, rpn));
expect (detector.parseControllerMessage (16, 6, 0, rpn));
expectEquals (rpn.channel, 16);
expectEquals (rpn.parameterNumber, 0);
expectEquals (rpn.value, 3);
expect (! rpn.isNRPN);
expect (rpn.is14BitValue);
}
beginTest ("invalid RPN (wrong order)");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (2, 6, 42, rpn));
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
}
beginTest ("14-bit RPN interspersed with unrelated CC messages");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (16, 3, 80, rpn));
expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
expect (! detector.parseControllerMessage (16, 4, 81, rpn));
expect (! detector.parseControllerMessage (16, 101, 0, rpn));
expect (! detector.parseControllerMessage (16, 5, 82, rpn));
expect (! detector.parseControllerMessage (16, 5, 83, rpn));
expect (! detector.parseControllerMessage (16, 38, 3, rpn));
expect (! detector.parseControllerMessage (16, 4, 84, rpn));
expect (! detector.parseControllerMessage (16, 3, 85, rpn));
expect (detector.parseControllerMessage (16, 6, 0, rpn));
expectEquals (rpn.channel, 16);
expectEquals (rpn.parameterNumber, 0);
expectEquals (rpn.value, 3);
expect (! rpn.isNRPN);
expect (rpn.is14BitValue);
}
beginTest ("14-bit NRPN");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (1, 98, 44, rpn));
expect (! detector.parseControllerMessage (1, 99 , 2, rpn));
expect (! detector.parseControllerMessage (1, 38, 94, rpn));
expect (detector.parseControllerMessage (1, 6, 1, rpn));
expectEquals (rpn.channel, 1);
expectEquals (rpn.parameterNumber, 300);
expectEquals (rpn.value, 222);
expect (rpn.isNRPN);
expect (rpn.is14BitValue);
}
beginTest ("reset");
{
MidiRPNDetector detector;
MidiRPNMessage rpn;
expect (! detector.parseControllerMessage (2, 101, 0, rpn));
detector.reset();
expect (! detector.parseControllerMessage (2, 100, 7, rpn));
expect (! detector.parseControllerMessage (2, 6, 42, rpn));
}
}
};
static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
//==============================================================================
class MidiRPNGeneratorTests : public UnitTest
{
public:
MidiRPNGeneratorTests()
: UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi)
{}
void runTest() override
{
beginTest ("generating RPN/NRPN");
{
{
MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
expectContainsRPN (buffer, 1, 23, 1337, true, true);
}
{
MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
expectContainsRPN (buffer, 16, 101, 34, false, false);
}
{
MidiRPNMessage message = { 16, 101, 34, false, false };
MidiBuffer buffer = MidiRPNGenerator::generate (message);
expectContainsRPN (buffer, message);
}
}
}
private:
//==============================================================================
void expectContainsRPN (const MidiBuffer& midiBuffer,
int channel,
int parameterNumber,
int value,
bool isNRPN,
bool is14BitValue)
{
MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
expectContainsRPN (midiBuffer, expected);
}
//==============================================================================
void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
{
MidiRPNMessage result = MidiRPNMessage();
MidiRPNDetector detector;
for (const auto metadata : midiBuffer)
{
const auto midiMessage = metadata.getMessage();
if (detector.parseControllerMessage (midiMessage.getChannel(),
midiMessage.getControllerNumber(),
midiMessage.getControllerValue(),
result))
break;
}
expectEquals (result.channel, expected.channel);
expectEquals (result.parameterNumber, expected.parameterNumber);
expectEquals (result.value, expected.value);
expect (result.isNRPN == expected.isNRPN);
expect (result.is14BitValue == expected.is14BitValue);
}
};
static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;
#endif
} // namespace juce

+ 154
- 0
libs/juce-current/source/juce_audio_basics/midi/juce_MidiRPN.h View File

@@ -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

+ 2288
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEInstrument.cpp
File diff suppressed because it is too large
View File


+ 413
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEInstrument.h View File

@@ -0,0 +1,413 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class represents an instrument handling MPE.
It has an MPE zone layout and maintains a state of currently
active (playing) notes and the values of their dimensions of expression.
You can trigger and modulate notes:
- by passing MIDI messages with the method processNextMidiEvent;
- by directly calling the methods noteOn, noteOff etc.
The class implements the channel and note management logic specified in
MPE. If you pass it a message, it will know what notes on what
channels (if any) should be affected by that message.
The class has a Listener class with the three callbacks MPENoteAdded,
MPENoteChanged, and MPENoteFinished. Implement such a
Listener class to react to note changes and trigger some functionality for
your application that depends on the MPE note state.
For example, you can use this class to write an MPE visualiser.
If you want to write a real-time audio synth with MPE functionality,
you should instead use the classes MPESynthesiserBase, which adds
the ability to render audio and to manage voices.
@see MPENote, MPEZoneLayout, MPESynthesiser
@tags{Audio}
*/
class JUCE_API MPEInstrument
{
public:
/** Constructor.
This will construct an MPE instrument with inactive lower and upper zones.
In order to process incoming MIDI, call setZoneLayout, define the layout
via MIDI RPN messages, or set the instrument to legacy mode.
*/
MPEInstrument() noexcept;
/** Destructor. */
virtual ~MPEInstrument();
//==============================================================================
/** Returns the current zone layout of the instrument.
This happens by value, to enforce thread-safety and class invariants.
Note: If the instrument is in legacy mode, the return value of this
method is unspecified.
*/
MPEZoneLayout getZoneLayout() const noexcept;
/** Re-sets the zone layout of the instrument to the one passed in.
As a side effect, this will discard all currently playing notes,
and call noteReleased for all of them.
This will also disable legacy mode in case it was enabled previously.
*/
void setZoneLayout (MPEZoneLayout newLayout);
/** Returns true if the given MIDI channel (1-16) is a note channel in any
of the MPEInstrument's MPE zones; false otherwise.
When in legacy mode, this will return true if the given channel is
contained in the current legacy mode channel range; false otherwise.
*/
bool isMemberChannel (int midiChannel) const noexcept;
/** Returns true if the given MIDI channel (1-16) is a master channel (channel
1 or 16).
In legacy mode, this will always return false.
*/
bool isMasterChannel (int midiChannel) const noexcept;
/** Returns true if the given MIDI channel (1-16) is used by any of the
MPEInstrument's MPE zones; false otherwise.
When in legacy mode, this will return true if the given channel is
contained in the current legacy mode channel range; false otherwise.
*/
bool isUsingChannel (int midiChannel) const noexcept;
//==============================================================================
/** The MPE note tracking mode. In case there is more than one note playing
simultaneously on the same MIDI channel, this determines which of these
notes will be modulated by an incoming MPE message on that channel
(pressure, pitchbend, or timbre).
The default is lastNotePlayedOnChannel.
*/
enum TrackingMode
{
lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */
lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */
highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */
allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */
};
/** Set the MPE tracking mode for the pressure dimension. */
void setPressureTrackingMode (TrackingMode modeToUse);
/** Set the MPE tracking mode for the pitchbend dimension. */
void setPitchbendTrackingMode (TrackingMode modeToUse);
/** Set the MPE tracking mode for the timbre dimension. */
void setTimbreTrackingMode (TrackingMode modeToUse);
//==============================================================================
/** Process a MIDI message and trigger the appropriate method calls
(noteOn, noteOff etc.)
You can override this method if you need some special MIDI message
treatment on top of the standard MPE logic implemented here.
*/
virtual void processNextMidiEvent (const MidiMessage& message);
//==============================================================================
/** Request a note-on on the given channel, with the given initial note
number and velocity.
If the message arrives on a valid note channel, this will create a
new MPENote and call the noteAdded callback.
*/
virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity);
/** Request a note-off.
If there is a matching playing note, this will release the note
(except if it is sustained by a sustain or sostenuto pedal) and call
the noteReleased callback.
*/
virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity);
/** Request a pitchbend on the given channel with the given value (in units
of MIDI pitchwheel position).
Internally, this will determine whether the pitchwheel move is a
per-note pitchbend or a master pitchbend (depending on midiChannel),
take the correct per-note or master pitchbend range of the affected MPE
zone, and apply the resulting pitchbend to the affected note(s) (if any).
*/
virtual void pitchbend (int midiChannel, MPEValue pitchbend);
/** Request a pressure change on the given channel with the given value.
This will modify the pressure dimension of the note currently held down
on this channel (if any). If the channel is a zone master channel,
the pressure change will be broadcast to all notes in this zone.
*/
virtual void pressure (int midiChannel, MPEValue value);
/** Request a third dimension (timbre) change on the given channel with the
given value.
This will modify the timbre dimension of the note currently held down
on this channel (if any). If the channel is a zone master channel,
the timbre change will be broadcast to all notes in this zone.
*/
virtual void timbre (int midiChannel, MPEValue value);
/** Request a poly-aftertouch change for a given note number.
The change will be broadcast to all notes sharing the channel and note
number of the change message.
*/
virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value);
/** Request a sustain pedal press or release.
If midiChannel is a zone's master channel, this will act on all notes in
that zone; otherwise, nothing will happen.
*/
virtual void sustainPedal (int midiChannel, bool isDown);
/** Request a sostenuto pedal press or release.
If midiChannel is a zone's master channel, this will act on all notes in
that zone; otherwise, nothing will happen.
*/
virtual void sostenutoPedal (int midiChannel, bool isDown);
/** Discard all currently playing notes.
This will also call the noteReleased listener callback for all of them.
*/
void releaseAllNotes();
//==============================================================================
/** Returns the number of MPE notes currently played by the instrument. */
int getNumPlayingNotes() const noexcept;
/** Returns the note at the given index.
If there is no such note, returns an invalid MPENote. The notes are sorted
such that the most recently added note is the last element.
*/
MPENote getNote (int index) const noexcept;
/** Returns the note currently playing on the given midiChannel with the
specified initial MIDI note number, if there is such a note. Otherwise,
this returns an invalid MPENote (check with note.isValid() before use!)
*/
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;
/** Returns the most recent note that is playing on the given midiChannel
(this will be the note which has received the most recent note-on without
a corresponding note-off), if there is such a note. Otherwise, this returns an
invalid MPENote (check with note.isValid() before use!)
*/
MPENote getMostRecentNote (int midiChannel) const noexcept;
/** Returns the most recent note that is not the note passed in. If there is no
such note, this returns an invalid MPENote (check with note.isValid() before use!).
This helper method might be useful for some custom voice handling algorithms.
*/
MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;
//==============================================================================
/** Derive from this class to be informed about any changes in the expressive
MIDI notes played by this instrument.
Note: This listener type receives its callbacks immediately, and not
via the message thread (so you might be for example in the MIDI thread).
Therefore you should never do heavy work such as graphics rendering etc.
inside those callbacks.
*/
class JUCE_API Listener
{
public:
/** Destructor. */
virtual ~Listener() = default;
/** Implement this callback to be informed whenever a new expressive MIDI
note is triggered.
*/
virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); }
/** Implement this callback to be informed whenever a currently playing
MPE note's pressure value changes.
*/
virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); }
/** Implement this callback to be informed whenever a currently playing
MPE note's pitchbend value changes.
Note: This can happen if the note itself is bent, if there is a
master channel pitchbend event, or if both occur simultaneously.
Call MPENote::getFrequencyInHertz to get the effective note frequency.
*/
virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); }
/** Implement this callback to be informed whenever a currently playing
MPE note's timbre value changes.
*/
virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); }
/** Implement this callback to be informed whether a currently playing
MPE note's key state (whether the key is down and/or the note is
sustained) has changed.
Note: If the key state changes to MPENote::off, noteReleased is
called instead.
*/
virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); }
/** Implement this callback to be informed whenever an MPE note
is released (either by a note-off message, or by a sustain/sostenuto
pedal release for a note that already received a note-off),
and should therefore stop playing.
*/
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); }
};
//==============================================================================
/** Adds a listener. */
void addListener (Listener* listenerToAdd);
/** Removes a listener. */
void removeListener (Listener* listenerToRemove);
//==============================================================================
/** Puts the instrument into legacy mode.
As a side effect, this will discard all currently playing notes,
and call noteReleased for all of them.
This special zone layout mode is for backwards compatibility with
non-MPE MIDI devices. In this mode, the instrument will ignore the
current MPE zone layout. It will instead take a range of MIDI channels
(default: all channels 1-16) and treat them as note channels, with no
master channel. MIDI channels outside of this range will be ignored.
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
Must be between 0 and 96, otherwise behaviour is undefined.
The default pitchbend range in legacy mode is +/- 2 semitones.
@param channelRange The range of MIDI channels to use for notes when in legacy mode.
The default is to use all MIDI channels (1-16).
To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
*/
void enableLegacyMode (int pitchbendRange = 2,
Range<int> channelRange = Range<int> (1, 17));
/** Returns true if the instrument is in legacy mode, false otherwise. */
bool isLegacyModeEnabled() const noexcept;
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
Range<int> getLegacyModeChannelRange() const noexcept;
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
void setLegacyModeChannelRange (Range<int> channelRange);
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
int getLegacyModePitchbendRange() const noexcept;
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
void setLegacyModePitchbendRange (int pitchbendRange);
protected:
//==============================================================================
CriticalSection lock;
private:
//==============================================================================
Array<MPENote> notes;
MPEZoneLayout zoneLayout;
ListenerList<Listener> listeners;
uint8 lastPressureLowerBitReceivedOnChannel[16];
uint8 lastTimbreLowerBitReceivedOnChannel[16];
bool isMemberChannelSustained[16];
struct LegacyMode
{
bool isEnabled;
Range<int> channelRange;
int pitchbendRange;
};
struct MPEDimension
{
TrackingMode trackingMode = lastNotePlayedOnChannel;
MPEValue lastValueReceivedOnChannel[16];
MPEValue MPENote::* value;
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
};
LegacyMode legacyMode;
MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
void updateDimension (int midiChannel, MPEDimension&, MPEValue);
void updateDimensionMaster (bool, MPEDimension&, MPEValue);
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue);
void callListenersDimensionChanged (const MPENote&, const MPEDimension&);
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const;
void processMidiNoteOnMessage (const MidiMessage&);
void processMidiNoteOffMessage (const MidiMessage&);
void processMidiPitchWheelMessage (const MidiMessage&);
void processMidiChannelPressureMessage (const MidiMessage&);
void processMidiControllerMessage (const MidiMessage&);
void processMidiResetAllControllersMessage (const MidiMessage&);
void processMidiAfterTouchMessage (const MidiMessage&);
void handlePressureMSB (int midiChannel, int value) noexcept;
void handlePressureLSB (int midiChannel, int value) noexcept;
void handleTimbreMSB (int midiChannel, int value) noexcept;
void handleTimbreLSB (int midiChannel, int value) noexcept;
void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto);
const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept;
MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept;
const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept;
MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept;
const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept;
MPENote* getLastNotePlayedPtr (int midiChannel) noexcept;
const MPENote* getHighestNotePtr (int midiChannel) const noexcept;
MPENote* getHighestNotePtr (int midiChannel) noexcept;
const MPENote* getLowestNotePtr (int midiChannel) const noexcept;
MPENote* getLowestNotePtr (int midiChannel) noexcept;
void updateNoteTotalPitchbend (MPENote&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument)
};
} // namespace juce

+ 238
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEMessages.cpp View File

@@ -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

+ 116
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEMessages.h View File

@@ -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

+ 127
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPENote.cpp View File

@@ -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

+ 184
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPENote.h View File

@@ -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

+ 338
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiser.cpp View File

@@ -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

+ 312
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiser.h View File

@@ -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

+ 376
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp View File

@@ -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

+ 215
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserBase.h View File

@@ -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

+ 50
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp View File

@@ -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

+ 191
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h View File

@@ -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

+ 494
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEUtils.cpp View File

@@ -0,0 +1,494 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse)
: zone (new MPEZoneLayout::Zone (zoneToUse)),
channelIncrement (zone->isLowerZone() ? 1 : -1),
numChannels (zone->numMemberChannels),
firstChannel (zone->getFirstMemberChannel()),
lastChannel (zone->getLastMemberChannel()),
midiChannelLastAssigned (firstChannel - channelIncrement)
{
// must be an active MPE zone!
jassert (numChannels > 0);
}
MPEChannelAssigner::MPEChannelAssigner (Range<int> channelRange)
: isLegacy (true),
channelIncrement (1),
numChannels (channelRange.getLength()),
firstChannel (channelRange.getStart()),
lastChannel (channelRange.getEnd() - 1),
midiChannelLastAssigned (firstChannel - channelIncrement)
{
// must have at least one channel!
jassert (! channelRange.isEmpty());
}
int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
{
if (numChannels <= 1)
return firstChannel;
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
{
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
{
midiChannelLastAssigned = ch;
midiChannels[ch].notes.add (noteNumber);
return ch;
}
}
for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
{
if (ch == lastChannel + channelIncrement) // loop wrap-around
ch = firstChannel;
if (midiChannels[ch].isFree())
{
midiChannelLastAssigned = ch;
midiChannels[ch].notes.add (noteNumber);
return ch;
}
if (ch == midiChannelLastAssigned)
break; // no free channels!
}
midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
return midiChannelLastAssigned;
}
void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
{
const auto removeNote = [] (MidiChannel& ch, int noteNum)
{
if (ch.notes.removeAllInstancesOf (noteNum) > 0)
{
ch.lastNotePlayed = noteNum;
return true;
}
return false;
};
if (midiChannel >= 0 && midiChannel < 17)
{
removeNote (midiChannels[midiChannel], noteNumber);
return;
}
for (auto& ch : midiChannels)
{
if (removeNote (ch, noteNumber))
return;
}
}
void MPEChannelAssigner::allNotesOff()
{
for (auto& ch : midiChannels)
{
if (ch.notes.size() > 0)
ch.lastNotePlayed = ch.notes.getLast();
ch.notes.clear();
}
}
int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
{
auto channelWithClosestNote = firstChannel;
int closestNoteDistance = 127;
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
{
for (auto note : midiChannels[ch].notes)
{
auto noteDistance = std::abs (note - noteNumber);
if (noteDistance > 0 && noteDistance < closestNoteDistance)
{
closestNoteDistance = noteDistance;
channelWithClosestNote = ch;
}
}
}
return channelWithClosestNote;
}
//==============================================================================
MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap)
: zone (zoneToRemap),
channelIncrement (zone.isLowerZone() ? 1 : -1),
firstChannel (zone.getFirstMemberChannel()),
lastChannel (zone.getLastMemberChannel())
{
// must be an active MPE zone!
jassert (zone.numMemberChannels > 0);
zeroArrays();
}
void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept
{
auto channel = message.getChannel();
if (! zone.isUsingChannelAsMemberChannel (channel))
return;
if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
{
clearSource (mpeSourceID);
return;
}
auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
if (messageIsNoteData (message))
{
++counter;
// fast path - no remap
if (applyRemapIfExisting (channel, sourceAndChannelID, message))
return;
// find existing remap
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
if (applyRemapIfExisting (chan, sourceAndChannelID, message))
return;
// no remap necessary
if (sourceAndChannel[channel] == notMPE)
{
lastUsed[channel] = counter;
sourceAndChannel[channel] = sourceAndChannelID;
return;
}
// remap source & channel to new channel
auto chan = getBestChanToReuse();
sourceAndChannel[chan] = sourceAndChannelID;
lastUsed[chan] = counter;
message.setChannel (chan);
}
}
void MPEChannelRemapper::reset() noexcept
{
for (auto& s : sourceAndChannel)
s = notMPE;
}
void MPEChannelRemapper::clearChannel (int channel) noexcept
{
sourceAndChannel[channel] = notMPE;
}
void MPEChannelRemapper::clearSource (uint32 mpeSourceID)
{
for (auto& s : sourceAndChannel)
{
if (uint32 (s >> 5) == mpeSourceID)
{
s = notMPE;
return;
}
}
}
bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
{
if (sourceAndChannel[channel] == sourceAndChannelID)
{
if (m.isNoteOff())
sourceAndChannel[channel] = notMPE;
else
lastUsed[channel] = counter;
m.setChannel (channel);
return true;
}
return false;
}
int MPEChannelRemapper::getBestChanToReuse() const noexcept
{
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
if (sourceAndChannel[chan] == notMPE)
return chan;
auto bestChan = firstChannel;
auto bestLastUse = counter;
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
{
if (lastUsed[chan] < bestLastUse)
{
bestLastUse = lastUsed[chan];
bestChan = chan;
}
}
return bestChan;
}
void MPEChannelRemapper::zeroArrays()
{
for (int i = 0; i < 17; ++i)
{
sourceAndChannel[i] = 0;
lastUsed[i] = 0;
}
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct MPEUtilsUnitTests : public UnitTest
{
MPEUtilsUnitTests()
: UnitTest ("MPE Utilities", UnitTestCategories::midi)
{}
void runTest() override
{
beginTest ("MPEChannelAssigner");
{
MPEZoneLayout layout;
// lower
{
layout.setLowerZone (15);
// lower zone
MPEChannelAssigner channelAssigner (layout.getLowerZone());
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 2; ch <= 16; ++ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
// all notes off
channelAssigner.allNotesOff();
// last note played
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
}
// upper
{
layout.setUpperZone (15);
// upper zone
MPEChannelAssigner channelAssigner (layout.getUpperZone());
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 15; ch >= 1; --ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
// all notes off
channelAssigner.allNotesOff();
// last note played
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
}
// legacy
{
MPEChannelAssigner channelAssigner;
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 1; ch <= 16; ++ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
// all notes off
channelAssigner.allNotesOff();
// last note played
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
}
}
beginTest ("MPEChannelRemapper");
{
// 3 different MPE 'sources', constant IDs
const int sourceID1 = 0;
const int sourceID2 = 1;
const int sourceID3 = 2;
MPEZoneLayout layout;
{
layout.setLowerZone (15);
// lower zone
MPEChannelRemapper channelRemapper (layout.getLowerZone());
// first source, shouldn't remap
for (int ch = 2; ch <= 16; ++ch)
{
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
expectEquals (noteOn.getChannel(), ch);
}
auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
// remap onto oldest last-used channel
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
expectEquals (noteOn.getChannel(), 2);
// remap onto oldest last-used channel
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
expectEquals (noteOn.getChannel(), 3);
// remap to correct channel for source ID
auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
expectEquals (noteOff.getChannel(), 3);
}
{
layout.setUpperZone (15);
// upper zone
MPEChannelRemapper channelRemapper (layout.getUpperZone());
// first source, shouldn't remap
for (int ch = 15; ch >= 1; --ch)
{
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
expectEquals (noteOn.getChannel(), ch);
}
auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
// remap onto oldest last-used channel
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
expectEquals (noteOn.getChannel(), 15);
// remap onto oldest last-used channel
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
expectEquals (noteOn.getChannel(), 14);
// remap to correct channel for source ID
auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
expectEquals (noteOff.getChannel(), 14);
}
}
}
};
static MPEUtilsUnitTests MPEUtilsUnitTests;
#endif
} // namespace juce

+ 153
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEUtils.h View File

@@ -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

+ 173
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEValue.cpp View File

@@ -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

+ 97
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEValue.h View File

@@ -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

+ 386
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp View File

@@ -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

+ 225
- 0
libs/juce-current/source/juce_audio_basics/mpe/juce_MPEZoneLayout.h View File

@@ -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

+ 330
- 0
libs/juce-current/source/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h View File

@@ -0,0 +1,330 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if ! DOXYGEN && (JUCE_MAC || JUCE_IOS)
struct CoreAudioLayouts
{
//==============================================================================
enum
{
coreAudioHOASN3DLayoutTag = (190U<<16) | 0 // kAudioChannelLayoutTag_HOA_ACN_SN3D
};
//==============================================================================
/** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet.
Note that this method cannot preserve the order of channels.
*/
static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout)
{
return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout));
}
/** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet.
Note that this method cannot preserve the order of channels.
*/
static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag)
{
return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag));
}
/** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag.
Note that this method cannot preserve the order of channels.
*/
static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set)
{
if (set.getAmbisonicOrder() >= 0)
return coreAudioHOASN3DLayoutTag | static_cast<unsigned> (set.size());
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
{
AudioChannelSet caSet;
for (int i = 0; i < numElementsInArray (tbl->channelTypes)
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
caSet.addChannel (tbl->channelTypes[i]);
if (caSet == set)
return tbl->tag;
}
return kAudioChannelLayoutTag_DiscreteInOrder | static_cast<AudioChannelLayoutTag> (set.size());
}
static const Array<AudioChannelLayoutTag>& getKnownCoreAudioTags()
{
static Array<AudioChannelLayoutTag> tags (createKnownCoreAudioTags());
return tags;
}
//==============================================================================
/** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */
static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout)
{
switch (layout.mChannelLayoutTag & 0xffff0000)
{
case kAudioChannelLayoutTag_UseChannelBitmap:
return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes();
case kAudioChannelLayoutTag_UseChannelDescriptions:
{
Array<AudioChannelSet::ChannelType> channels;
for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i)
channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel));
// different speaker mappings may point to the same JUCE speaker so fill up
// this array with discrete channels
for (int j = 0; channels.size() < static_cast<int> (layout.mNumberChannelDescriptions); ++j)
channels.addIfNotAlreadyThere (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + j));
return channels;
}
case kAudioChannelLayoutTag_DiscreteInOrder:
return AudioChannelSet::discreteChannels (static_cast<int> (layout.mChannelLayoutTag) & 0xffff).getChannelTypes();
default:
break;
}
return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag);
}
static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag)
{
// You need to specify the full AudioChannelLayout when using
// the UseChannelBitmap and UseChannelDescriptions layout tag
jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions);
Array<AudioChannelSet::ChannelType> speakers;
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
{
if (tag == tbl->tag)
{
for (int i = 0; i < numElementsInArray (tbl->channelTypes)
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
speakers.add (tbl->channelTypes[i]);
return speakers;
}
}
auto numChannels = tag & 0xffff;
if (tag >= coreAudioHOASN3DLayoutTag && tag <= (coreAudioHOASN3DLayoutTag | 0xffff))
{
auto sqrtMinusOne = std::sqrt (static_cast<float> (numChannels)) - 1.0f;
auto ambisonicOrder = jmax (0, static_cast<int> (std::floor (sqrtMinusOne)));
if (static_cast<float> (ambisonicOrder) == sqrtMinusOne)
return AudioChannelSet::ambisonic (ambisonicOrder).getChannelTypes();
}
for (UInt32 i = 0; i < numChannels; ++i)
speakers.add (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + i));
return speakers;
}
private:
//==============================================================================
struct LayoutTagSpeakerList
{
AudioChannelLayoutTag tag;
AudioChannelSet::ChannelType channelTypes[16];
};
static Array<AudioChannelLayoutTag> createKnownCoreAudioTags()
{
Array<AudioChannelLayoutTag> tags;
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
tags.addIfNotAlreadyThere (tbl->tag);
for (unsigned order = 0; order <= 5; ++order)
tags.addIfNotAlreadyThere (coreAudioHOASN3DLayoutTag | ((order + 1) * (order + 1)));
return tags;
}
//==============================================================================
// This list has been derived from https://pastebin.com/24dQ4BPJ
// Apple channel labels have been replaced by JUCE channel names
// This means that some layouts will be identical in JUCE but not in CoreAudio
// In Apple's official definition the following tags exist with the same speaker layout and order
// even when *not* represented in JUCE channels
// kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo
// kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal
// kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic
// kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal
struct SpeakerLayoutTable : AudioChannelSet // save us some typing
{
static LayoutTagSpeakerList* get() noexcept
{
static LayoutTagSpeakerList tbl[] = {
// list layouts for which there is a corresponding named AudioChannelSet first
{ kAudioChannelLayoutTag_Mono, { centre } },
{ kAudioChannelLayoutTag_Stereo, { left, right } },
{ kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } },
{ kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } },
{ kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } },
{ kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } },
{ kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } },
{ kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } },
{ kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } },
{ kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } },
{ kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } },
{ kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } },
{ kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } },
// more uncommon layouts
{ kAudioChannelLayoutTag_StereoHeadphones, { left, right } },
{ kAudioChannelLayoutTag_MatrixStereo, { left, right } },
{ kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } },
{ kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } },
{ kAudioChannelLayoutTag_Binaural, { left, right } },
{ kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } },
{ kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } },
{ kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } },
{ kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } },
{ kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } },
{ kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } },
{ kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} },
{ kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_DVD_4, { left, right, LFE } },
{ kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } },
{ kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } },
{ kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } },
{ kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } },
{ kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } },
{ kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
{ kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
{ kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } },
{ kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } },
{ kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } },
{ kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } },
{ kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } },
{ kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } },
{ kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } },
{ kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } },
{ kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } },
{ kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
{ kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
{ kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } },
{ kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } },
{ kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } },
{ kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } },
{ kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
{ kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } },
{ kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
{ kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
{ kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } },
{ kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } },
{ kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } },
{ kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } },
{ kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } },
{ kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } },
{ kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } },
{ kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
{ kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
{ kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } },
{ kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
{ kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } },
{ 0, {} }
};
return tbl;
}
};
//==============================================================================
static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept
{
if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535)
{
const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0;
return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum);
}
switch (label)
{
case kAudioChannelLabel_Center:
case kAudioChannelLabel_Mono: return AudioChannelSet::centre;
case kAudioChannelLabel_Left:
case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left;
case kAudioChannelLabel_Right:
case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right;
case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE;
case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround;
case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround;
case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre;
case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre;
case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround;
case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide;
case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide;
case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle;
case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft;
case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight;
case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre;
case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft;
case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear;
case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight;
case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear;
case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre;
case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2;
case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft;
case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight;
case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW;
case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX;
case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY;
case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ;
default: return AudioChannelSet::unknown;
}
}
};
#endif
} // namespace juce

+ 179
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_AudioSource.h View File

@@ -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

+ 315
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_BufferingAudioSource.cpp View File

@@ -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

+ 119
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_BufferingAudioSource.h View File

@@ -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

+ 187
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp View File

@@ -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

+ 141
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h View File

@@ -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

+ 80
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp View File

@@ -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

+ 68
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_IIRFilterAudioSource.h View File

@@ -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

+ 100
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_MemoryAudioSource.cpp View File

@@ -0,0 +1,100 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MemoryAudioSource::MemoryAudioSource (AudioBuffer<float>& bufferToUse, bool copyMemory, bool shouldLoop)
: isCurrentlyLooping (shouldLoop)
{
if (copyMemory)
buffer.makeCopyOf (bufferToUse);
else
buffer.setDataToReferTo (bufferToUse.getArrayOfWritePointers(),
bufferToUse.getNumChannels(),
bufferToUse.getNumSamples());
}
//==============================================================================
void MemoryAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/)
{
position = 0;
}
void MemoryAudioSource::releaseResources() {}
void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
auto& dst = *bufferToFill.buffer;
auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels());
auto max = 0, pos = 0;
auto n = buffer.getNumSamples(), m = bufferToFill.numSamples;
int i;
for (i = position; (i < n || isCurrentlyLooping) && (pos < m); i += max)
{
max = jmin (m - pos, n - (i % n));
int ch = 0;
for (; ch < channels; ++ch)
dst.copyFrom (ch, bufferToFill.startSample + pos, buffer, ch, i % n, max);
for (; ch < dst.getNumChannels(); ++ch)
dst.clear (ch, bufferToFill.startSample + pos, max);
pos += max;
}
if (pos < m)
dst.clear (bufferToFill.startSample + pos, m - pos);
position = (i % n);
}
//==============================================================================
void MemoryAudioSource::setNextReadPosition (int64 newPosition)
{
position = (int) newPosition;
}
int64 MemoryAudioSource::getNextReadPosition() const
{
return position;
}
int64 MemoryAudioSource::getTotalLength() const
{
return buffer.getNumSamples();
}
//==============================================================================
bool MemoryAudioSource::isLooping() const
{
return isCurrentlyLooping;
}
void MemoryAudioSource::setLooping (bool shouldLoop)
{
isCurrentlyLooping = shouldLoop;
}
} // namespace juce

+ 82
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_MemoryAudioSource.h View File

@@ -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

+ 158
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_MixerAudioSource.cpp View File

@@ -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

+ 99
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_MixerAudioSource.h View File

@@ -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

+ 76
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_PositionableAudioSource.h View File

@@ -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

+ 265
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp View File

@@ -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

+ 106
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ResamplingAudioSource.h View File

@@ -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

+ 83
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ReverbAudioSource.cpp View File

@@ -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

+ 74
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ReverbAudioSource.h View File

@@ -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

+ 78
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp View File

@@ -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

+ 71
- 0
libs/juce-current/source/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h View File

@@ -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

+ 585
- 0
libs/juce-current/source/juce_audio_basics/synthesisers/juce_Synthesiser.cpp View File

@@ -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

+ 645
- 0
libs/juce-current/source/juce_audio_basics/synthesisers/juce_Synthesiser.h View File

@@ -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

+ 248
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_ADSR.h View File

@@ -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

+ 112
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_Decibels.h View File

@@ -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

+ 500
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_GenericInterpolator.h View File

@@ -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

+ 336
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_IIRFilter.cpp View File

@@ -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

+ 217
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_IIRFilter.h View File

@@ -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

+ 191
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_Interpolators.cpp View File

@@ -0,0 +1,191 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
class InterpolatorTests : public UnitTest
{
public:
InterpolatorTests()
: UnitTest ("InterpolatorTests", UnitTestCategories::audio)
{
}
private:
template<typename InterpolatorType>
void runInterplatorTests (const String& interpolatorName)
{
auto createGaussian = [] (std::vector<float>& destination, float scale, float centreInSamples, float width)
{
for (size_t i = 0; i < destination.size(); ++i)
{
auto x = (((float) i) - centreInSamples) * width;
destination[i] = std::exp (-(x * x));
}
FloatVectorOperations::multiply (destination.data(), scale, (int) destination.size());
};
auto findGaussianPeak = [] (const std::vector<float>& input) -> float
{
auto max = std::max_element (std::begin (input), std::end (input));
auto maxPrev = max - 1;
jassert (maxPrev >= std::begin (input));
auto maxNext = max + 1;
jassert (maxNext < std::end (input));
auto quadraticMaxLoc = (*maxPrev - *maxNext) / (2.0f * ((*maxNext + *maxPrev) - (2.0f * *max)));
return quadraticMaxLoc + (float) std::distance (std::begin (input), max);
};
auto expectAllElementsWithin = [this] (const std::vector<float>& v1, const std::vector<float>& v2, float tolerance)
{
expectEquals ((int) v1.size(), (int) v2.size());
for (size_t i = 0; i < v1.size(); ++i)
expectWithinAbsoluteError (v1[i], v2[i], tolerance);
};
InterpolatorType interpolator;
constexpr size_t inputSize = 1001;
static_assert (inputSize > 800 + InterpolatorType::getBaseLatency(),
"The test InterpolatorTests input buffer is too small");
std::vector<float> input (inputSize);
constexpr auto inputGaussianMidpoint = (float) (inputSize - 1) / 2.0f;
constexpr auto inputGaussianValueAtEnds = 0.000001f;
const auto inputGaussianWidth = std::sqrt (-std::log (inputGaussianValueAtEnds)) / inputGaussianMidpoint;
createGaussian (input, 1.0f, inputGaussianMidpoint, inputGaussianWidth);
for (auto speedRatio : { 0.4, 0.8263, 1.0, 1.05, 1.2384, 1.6 })
{
const auto expectedGaussianMidpoint = (inputGaussianMidpoint + InterpolatorType::getBaseLatency()) / (float) speedRatio;
const auto expectedGaussianWidth = inputGaussianWidth * (float) speedRatio;
const auto outputBufferSize = (size_t) std::floor ((float) input.size() / speedRatio);
for (int numBlocks : { 1, 5 })
{
const auto inputBlockSize = (float) input.size() / (float) numBlocks;
const auto outputBlockSize = (int) std::floor (inputBlockSize / speedRatio);
std::vector<float> output (outputBufferSize, std::numeric_limits<float>::min());
beginTest (interpolatorName + " process " + String (numBlocks) + " blocks ratio " + String (speedRatio));
interpolator.reset();
{
auto* inputPtr = input.data();
auto* outputPtr = output.data();
for (int i = 0; i < numBlocks; ++i)
{
auto numInputSamplesRead = interpolator.process (speedRatio, inputPtr, outputPtr, outputBlockSize);
inputPtr += numInputSamplesRead;
outputPtr += outputBlockSize;
}
}
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f);
std::vector<float> expectedOutput (output.size());
createGaussian (expectedOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth);
expectAllElementsWithin (output, expectedOutput, 0.02f);
beginTest (interpolatorName + " process adding " + String (numBlocks) + " blocks ratio " + String (speedRatio));
interpolator.reset();
constexpr float addingGain = 0.7384f;
{
auto* inputPtr = input.data();
auto* outputPtr = output.data();
for (int i = 0; i < numBlocks; ++i)
{
auto numInputSamplesRead = interpolator.processAdding (speedRatio, inputPtr, outputPtr, outputBlockSize, addingGain);
inputPtr += numInputSamplesRead;
outputPtr += outputBlockSize;
}
}
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f);
std::vector<float> additionalOutput (output.size());
createGaussian (additionalOutput, addingGain, expectedGaussianMidpoint, expectedGaussianWidth);
FloatVectorOperations::add (expectedOutput.data(), additionalOutput.data(), (int) additionalOutput.size());
expectAllElementsWithin (output, expectedOutput, 0.02f);
}
beginTest (interpolatorName + " process wrap 0 ratio " + String (speedRatio));
std::vector<float> doubleLengthOutput (2 * outputBufferSize, std::numeric_limits<float>::min());
interpolator.reset();
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(),
(int) input.size(), 0);
std::vector<float> expectedDoubleLengthOutput (doubleLengthOutput.size());
createGaussian (expectedDoubleLengthOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth);
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f);
beginTest (interpolatorName + " process wrap double ratio " + String (speedRatio));
interpolator.reset();
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(),
(int) input.size(), (int) input.size());
std::vector<float> secondGaussian (doubleLengthOutput.size());
createGaussian (secondGaussian, 1.0f, expectedGaussianMidpoint + (float) outputBufferSize, expectedGaussianWidth);
FloatVectorOperations::add (expectedDoubleLengthOutput.data(), secondGaussian.data(), (int) expectedDoubleLengthOutput.size());
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f);
}
}
public:
void runTest() override
{
runInterplatorTests<WindowedSincInterpolator> ("WindowedSincInterpolator");
runInterplatorTests<LagrangeInterpolator> ("LagrangeInterpolator");
runInterplatorTests<CatmullRomInterpolator> ("CatmullRomInterpolator");
runInterplatorTests<LinearInterpolator> ("LinearInterpolator");
}
};
static InterpolatorTests interpolatorTests;
#endif
} // namespace juce

+ 245
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_Interpolators.h View File

@@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
A collection of different interpolators for resampling streams of floats.
@see GenericInterpolator, WindowedSincInterpolator, LagrangeInterpolator,
CatmullRomInterpolator, LinearInterpolator, ZeroOrderHoldInterpolator
@tags{Audio}
*/
class Interpolators
{
private:
struct WindowedSincTraits
{
static constexpr float algorithmicLatency = 100.0f;
static forcedinline float windowedSinc (float firstFrac, int index) noexcept
{
auto index2 = index + 1;
auto frac = firstFrac;
auto value1 = lookupTable[index];
auto value2 = lookupTable[index2];
return value1 + (frac * (value2 - value1));
}
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int indexBuffer) noexcept
{
const int numCrossings = 100;
const float floatCrossings = (float) numCrossings;
float result = 0.0f;
auto samplePosition = indexBuffer;
float firstFrac = 0.0f;
float lastSincPosition = -1.0f;
int index = 0, sign = -1;
for (int i = -numCrossings; i <= numCrossings; ++i)
{
auto sincPosition = (1.0f - offset) + (float) i;
if (i == -numCrossings || (sincPosition >= 0 && lastSincPosition < 0))
{
auto indexFloat = (sincPosition >= 0.f ? sincPosition : -sincPosition) * 100.0f;
auto indexFloored = std::floor (indexFloat);
index = (int) indexFloored;
firstFrac = indexFloat - indexFloored;
sign = (sincPosition < 0 ? -1 : 1);
}
if (sincPosition == 0.0f)
result += inputs[samplePosition];
else if (sincPosition < floatCrossings && sincPosition > -floatCrossings)
result += inputs[samplePosition] * windowedSinc (firstFrac, index);
if (++samplePosition == numCrossings * 2)
samplePosition = 0;
lastSincPosition = sincPosition;
index += 100 * sign;
}
return result;
}
static const float lookupTable[10001];
};
struct LagrangeTraits
{
static constexpr float algorithmicLatency = 2.0f;
static float valueAtOffset (const float*, float, int) noexcept;
};
struct CatmullRomTraits
{
//==============================================================================
static constexpr float algorithmicLatency = 2.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept
{
auto y0 = inputs[index]; if (++index == 4) index = 0;
auto y1 = inputs[index]; if (++index == 4) index = 0;
auto y2 = inputs[index]; if (++index == 4) index = 0;
auto y3 = inputs[index];
auto halfY0 = 0.5f * y0;
auto halfY3 = 0.5f * y3;
return y1 + offset * ((0.5f * y2 - halfY0)
+ (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1))
+ (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2))))));
}
};
struct LinearTraits
{
static constexpr float algorithmicLatency = 1.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept
{
auto y0 = inputs[index];
auto y1 = inputs[index == 0 ? 1 : 0];
return y1 * offset + y0 * (1.0f - offset);
}
};
struct ZeroOrderHoldTraits
{
static constexpr float algorithmicLatency = 0.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float, int) noexcept
{
return inputs[0];
}
};
public:
using WindowedSinc = GenericInterpolator<WindowedSincTraits, 200>;
using Lagrange = GenericInterpolator<LagrangeTraits, 5>;
using CatmullRom = GenericInterpolator<CatmullRomTraits, 4>;
using Linear = GenericInterpolator<LinearTraits, 2>;
using ZeroOrderHold = GenericInterpolator<ZeroOrderHoldTraits, 1>;
};
//==============================================================================
/**
An interpolator for resampling a stream of floats using high order windowed
(hann) sinc interpolation, recommended for high quality resampling.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own LinearInterpolator object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using WindowedSincInterpolator = Interpolators::WindowedSinc;
/**
An interpolator for resampling a stream of floats using 4-point lagrange interpolation.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own LagrangeInterpolator object.
@see GenericInterpolator
@see CatmullRomInterpolator, WindowedSincInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using LagrangeInterpolator = Interpolators::Lagrange;
/**
An interpolator for resampling a stream of floats using Catmull-Rom interpolation.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own CatmullRomInterpolator object.
@see GenericInterpolator
@see LagrangeInterpolator, WindowedSincInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using CatmullRomInterpolator = Interpolators::CatmullRom;
/**
An interpolator for resampling a stream of floats using linear interpolation.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own LinearInterpolator object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using LinearInterpolator = Interpolators::Linear;
/**
An interpolator for resampling a stream of floats using zero order hold
interpolation.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own ZeroOrderHoldInterpolator
object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
LinearInterpolator
@tags{Audio}
*/
using ZeroOrderHoldInterpolator = Interpolators::ZeroOrderHold;
} // namespace juce

+ 62
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp View File

@@ -0,0 +1,62 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
template <int k>
struct LagrangeResampleHelper
{
static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); }
};
template<>
struct LagrangeResampleHelper<0>
{
static forcedinline void calc (float&, float) noexcept {}
};
template <int k>
static float calcCoefficient (float input, float offset) noexcept
{
LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset);
LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset);
LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset);
LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset);
LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset);
return input;
}
float Interpolators::LagrangeTraits::valueAtOffset (const float* inputs, float offset, int index) noexcept
{
float result = 0.0f;
result += calcCoefficient<0> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<1> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<2> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<3> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<4> (inputs[index], offset);
return result;
}
} // namespace juce

+ 313
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_Reverb.h View File

@@ -0,0 +1,313 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Performs a simple reverb effect on a stream of audio data.
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
apply the reverb to your audio data.
@see ReverbAudioSource
@tags{Audio}
*/
class Reverb
{
public:
//==============================================================================
Reverb()
{
setParameters (Parameters());
setSampleRate (44100.0);
}
//==============================================================================
/** Holds the parameters being used by a Reverb object. */
struct Parameters
{
float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */
float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */
float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5
put the reverb into a continuous feedback loop. */
};
//==============================================================================
/** Returns the reverb's current parameters. */
const Parameters& getParameters() const noexcept { return parameters; }
/** Applies a new set of parameters to the reverb.
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
the process method, you may get artifacts.
*/
void setParameters (const Parameters& newParams)
{
const float wetScaleFactor = 3.0f;
const float dryScaleFactor = 2.0f;
const float wet = newParams.wetLevel * wetScaleFactor;
dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor);
wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width));
wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width));
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
parameters = newParams;
updateDamping();
}
//==============================================================================
/** Sets the sample rate that will be used for the reverb.
You must call this before the process methods, in order to tell it the correct sample rate.
*/
void setSampleRate (const double sampleRate)
{
jassert (sampleRate > 0);
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
static const short allPassTunings[] = { 556, 441, 341, 225 };
const int stereoSpread = 23;
const int intSampleRate = (int) sampleRate;
for (int i = 0; i < numCombs; ++i)
{
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
}
for (int i = 0; i < numAllPasses; ++i)
{
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
}
const double smoothTime = 0.01;
damping .reset (sampleRate, smoothTime);
feedback.reset (sampleRate, smoothTime);
dryGain .reset (sampleRate, smoothTime);
wetGain1.reset (sampleRate, smoothTime);
wetGain2.reset (sampleRate, smoothTime);
}
/** Clears the reverb's buffers. */
void reset()
{
for (int j = 0; j < numChannels; ++j)
{
for (int i = 0; i < numCombs; ++i)
comb[j][i].clear();
for (int i = 0; i < numAllPasses; ++i)
allPass[j][i].clear();
}
}
//==============================================================================
/** Applies the reverb to two stereo channels of audio data. */
void processStereo (float* const left, float* const right, const int numSamples) noexcept
{
jassert (left != nullptr && right != nullptr);
for (int i = 0; i < numSamples; ++i)
{
const float input = (left[i] + right[i]) * gain;
float outL = 0, outR = 0;
const float damp = damping.getNextValue();
const float feedbck = feedback.getNextValue();
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
{
outL += comb[0][j].process (input, damp, feedbck);
outR += comb[1][j].process (input, damp, feedbck);
}
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
{
outL = allPass[0][j].process (outL);
outR = allPass[1][j].process (outR);
}
const float dry = dryGain.getNextValue();
const float wet1 = wetGain1.getNextValue();
const float wet2 = wetGain2.getNextValue();
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
}
}
/** Applies the reverb to a single mono channel of audio data. */
void processMono (float* const samples, const int numSamples) noexcept
{
jassert (samples != nullptr);
for (int i = 0; i < numSamples; ++i)
{
const float input = samples[i] * gain;
float output = 0;
const float damp = damping.getNextValue();
const float feedbck = feedback.getNextValue();
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
output += comb[0][j].process (input, damp, feedbck);
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
output = allPass[0][j].process (output);
const float dry = dryGain.getNextValue();
const float wet1 = wetGain1.getNextValue();
samples[i] = output * wet1 + samples[i] * dry;
}
}
private:
//==============================================================================
static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; }
void updateDamping() noexcept
{
const float roomScaleFactor = 0.28f;
const float roomOffset = 0.7f;
const float dampScaleFactor = 0.4f;
if (isFrozen (parameters.freezeMode))
setDamping (0.0f, 1.0f);
else
setDamping (parameters.damping * dampScaleFactor,
parameters.roomSize * roomScaleFactor + roomOffset);
}
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept
{
damping.setTargetValue (dampingToUse);
feedback.setTargetValue (roomSizeToUse);
}
//==============================================================================
class CombFilter
{
public:
CombFilter() noexcept {}
void setSize (const int size)
{
if (size != bufferSize)
{
bufferIndex = 0;
buffer.malloc (size);
bufferSize = size;
}
clear();
}
void clear() noexcept
{
last = 0;
buffer.clear ((size_t) bufferSize);
}
float process (const float input, const float damp, const float feedbackLevel) noexcept
{
const float output = buffer[bufferIndex];
last = (output * (1.0f - damp)) + (last * damp);
JUCE_UNDENORMALISE (last);
float temp = input + (last * feedbackLevel);
JUCE_UNDENORMALISE (temp);
buffer[bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return output;
}
private:
HeapBlock<float> buffer;
int bufferSize = 0, bufferIndex = 0;
float last = 0.0f;
JUCE_DECLARE_NON_COPYABLE (CombFilter)
};
//==============================================================================
class AllPassFilter
{
public:
AllPassFilter() noexcept {}
void setSize (const int size)
{
if (size != bufferSize)
{
bufferIndex = 0;
buffer.malloc (size);
bufferSize = size;
}
clear();
}
void clear() noexcept
{
buffer.clear ((size_t) bufferSize);
}
float process (const float input) noexcept
{
const float bufferedValue = buffer [bufferIndex];
float temp = input + (bufferedValue * 0.5f);
JUCE_UNDENORMALISE (temp);
buffer [bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return bufferedValue - input;
}
private:
HeapBlock<float> buffer;
int bufferSize = 0, bufferIndex = 0;
JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
};
//==============================================================================
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
Parameters parameters;
float gain;
CombFilter comb [numChannels][numCombs];
AllPassFilter allPass [numChannels][numAllPasses];
SmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
};
} // namespace juce

+ 92
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_SmoothedValue.cpp View File

@@ -0,0 +1,92 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Linear>> commonLinearSmoothedValueTests;
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Multiplicative>> commonMultiplicativeSmoothedValueTests;
class SmoothedValueTests : public UnitTest
{
public:
SmoothedValueTests()
: UnitTest ("SmoothedValueTests", UnitTestCategories::smoothedValues)
{}
void runTest() override
{
beginTest ("Linear moving target");
{
SmoothedValue<float, ValueSmoothingTypes::Linear> sv;
sv.reset (12);
float initialValue = 0.0f;
sv.setCurrentAndTargetValue (initialValue);
sv.setTargetValue (1.0f);
auto delta = sv.getNextValue() - initialValue;
sv.skip (6);
auto newInitialValue = sv.getCurrentValue();
sv.setTargetValue (newInitialValue + 2.0f);
auto doubleDelta = sv.getNextValue() - newInitialValue;
expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f);
}
beginTest ("Multiplicative curve");
{
SmoothedValue<double, ValueSmoothingTypes::Multiplicative> sv;
auto numSamples = 12;
AudioBuffer<double> values (2, numSamples + 1);
sv.reset (numSamples);
sv.setCurrentAndTargetValue (1.0);
sv.setTargetValue (2.0f);
values.setSample (0, 0, sv.getCurrentValue());
for (int i = 1; i < values.getNumSamples(); ++i)
values.setSample (0, i, sv.getNextValue());
sv.setTargetValue (1.0f);
values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue());
for (int i = values.getNumSamples() - 2; i >= 0 ; --i)
values.setSample (1, i, sv.getNextValue());
for (int i = 0; i < values.getNumSamples(); ++i)
expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9);
}
}
};
static SmoothedValueTests smoothedValueTests;
#endif
} // namespace juce

+ 630
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_SmoothedValue.h View File

@@ -0,0 +1,630 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A base class for the smoothed value classes.
This class is used to provide common functionality to the SmoothedValue and
dsp::LogRampedValue classes.
@tags{Audio}
*/
template <typename SmoothedValueType>
class SmoothedValueBase
{
private:
//==============================================================================
template <typename T> struct FloatTypeHelper;
template <template <typename> class SmoothedValueClass, typename FloatType>
struct FloatTypeHelper <SmoothedValueClass <FloatType>>
{
using Type = FloatType;
};
template <template <typename, typename> class SmoothedValueClass, typename FloatType, typename SmoothingType>
struct FloatTypeHelper <SmoothedValueClass <FloatType, SmoothingType>>
{
using Type = FloatType;
};
public:
using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type;
//==============================================================================
/** Constructor. */
SmoothedValueBase() = default;
virtual ~SmoothedValueBase() {}
//==============================================================================
/** Returns true if the current value is currently being interpolated. */
bool isSmoothing() const noexcept { return countdown > 0; }
/** Returns the current value of the ramp. */
FloatType getCurrentValue() const noexcept { return currentValue; }
//==============================================================================
/** Returns the target value towards which the smoothed value is currently moving. */
FloatType getTargetValue() const noexcept { return target; }
/** Sets the current value and the target value.
@param newValue the new value to take
*/
void setCurrentAndTargetValue (FloatType newValue)
{
target = currentValue = newValue;
countdown = 0;
}
//==============================================================================
/** Applies a smoothed gain to a stream of samples
S[i] *= gain
@param samples Pointer to a raw array of samples
@param numSamples Length of array of samples
*/
void applyGain (FloatType* samples, int numSamples) noexcept
{
jassert (numSamples >= 0);
if (isSmoothing())
{
for (int i = 0; i < numSamples; ++i)
samples[i] *= getNextSmoothedValue();
}
else
{
FloatVectorOperations::multiply (samples, target, numSamples);
}
}
/** Computes output as a smoothed gain applied to a stream of samples.
Sout[i] = Sin[i] * gain
@param samplesOut A pointer to a raw array of output samples
@param samplesIn A pointer to a raw array of input samples
@param numSamples The length of the array of samples
*/
void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept
{
jassert (numSamples >= 0);
if (isSmoothing())
{
for (int i = 0; i < numSamples; ++i)
samplesOut[i] = samplesIn[i] * getNextSmoothedValue();
}
else
{
FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples);
}
}
/** Applies a smoothed gain to a buffer */
void applyGain (AudioBuffer<FloatType>& buffer, int numSamples) noexcept
{
jassert (numSamples >= 0);
if (isSmoothing())
{
if (buffer.getNumChannels() == 1)
{
auto* samples = buffer.getWritePointer (0);
for (int i = 0; i < numSamples; ++i)
samples[i] *= getNextSmoothedValue();
}
else
{
for (auto i = 0; i < numSamples; ++i)
{
auto gain = getNextSmoothedValue();
for (int channel = 0; channel < buffer.getNumChannels(); channel++)
buffer.setSample (channel, i, buffer.getSample (channel, i) * gain);
}
}
}
else
{
buffer.applyGain (0, numSamples, target);
}
}
private:
//==============================================================================
FloatType getNextSmoothedValue() noexcept
{
return static_cast <SmoothedValueType*> (this)->getNextValue();
}
protected:
//==============================================================================
FloatType currentValue = 0;
FloatType target = currentValue;
int countdown = 0;
};
//==============================================================================
/**
A namespace containing a set of types used for specifying the smoothing
behaviour of the SmoothedValue class.
For example:
@code
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> frequency (1.0f);
@endcode
*/
namespace ValueSmoothingTypes
{
/**
Used to indicate a linear smoothing between values.
@tags{Audio}
*/
struct Linear {};
/**
Used to indicate a smoothing between multiplicative values.
@tags{Audio}
*/
struct Multiplicative {};
}
//==============================================================================
/**
A utility class for values that need smoothing to avoid audio glitches.
A ValueSmoothingTypes::Linear template parameter selects linear smoothing,
which increments the SmoothedValue linearly towards its target value.
@code
SmoothedValue<float, ValueSmoothingTypes::Linear> yourSmoothedValue;
@endcode
A ValueSmoothingTypes::Multiplicative template parameter selects
multiplicative smoothing increments towards the target value.
@code
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue;
@endcode
Multiplicative smoothing is useful when you are dealing with
exponential/logarithmic values like volume in dB or frequency in Hz. For
example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the
frequency with an equal temperament tuning across the octave. A 10 step
smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in
increments of 1 dB.
Note that when you are using multiplicative smoothing you cannot ever reach a
target value of zero!
@tags{Audio}
*/
template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
{
public:
//==============================================================================
/** Constructor. */
SmoothedValue() noexcept
: SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1))
{
}
/** Constructor. */
SmoothedValue (FloatType initialValue) noexcept
{
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && initialValue == 0));
// Visual Studio can't handle base class initialisation with CRTP
this->currentValue = initialValue;
this->target = this->currentValue;
}
//==============================================================================
/** Reset to a new sample rate and ramp length.
@param sampleRate The sample rate
@param rampLengthInSeconds The duration of the ramp in seconds
*/
void reset (double sampleRate, double rampLengthInSeconds) noexcept
{
jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
reset ((int) std::floor (rampLengthInSeconds * sampleRate));
}
/** Set a new ramp length directly in samples.
@param numSteps The number of samples over which the ramp should be active
*/
void reset (int numSteps) noexcept
{
stepsToTarget = numSteps;
this->setCurrentAndTargetValue (this->target);
}
//==============================================================================
/** Set the next value to ramp towards.
@param newValue The new target value
*/
void setTargetValue (FloatType newValue) noexcept
{
if (newValue == this->target)
return;
if (stepsToTarget <= 0)
{
this->setCurrentAndTargetValue (newValue);
return;
}
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0));
this->target = newValue;
this->countdown = stepsToTarget;
setStepSize();
}
//==============================================================================
/** Compute the next value.
@returns Smoothed value
*/
FloatType getNextValue() noexcept
{
if (! this->isSmoothing())
return this->target;
--(this->countdown);
if (this->isSmoothing())
setNextValue();
else
this->currentValue = this->target;
return this->currentValue;
}
//==============================================================================
/** Skip the next numSamples samples.
This is identical to calling getNextValue numSamples times. It returns
the new current value.
@see getNextValue
*/
FloatType skip (int numSamples) noexcept
{
if (numSamples >= this->countdown)
{
this->setCurrentAndTargetValue (this->target);
return this->target;
}
skipCurrentValue (numSamples);
this->countdown -= numSamples;
return this->currentValue;
}
//==============================================================================
/** THIS FUNCTION IS DEPRECATED.
Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead:
lsv.setValue (x, false); -> lsv.setTargetValue (x);
lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x);
@param newValue The new target value
@param force If true, the value will be set immediately, bypassing the ramp
*/
JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept,
{
if (force)
{
this->setCurrentAndTargetValue (newValue);
return;
}
setTargetValue (newValue);
})
private:
//==============================================================================
template <typename T>
using LinearVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Linear>::value, void>::type;
template <typename T>
using MultiplicativeVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Multiplicative>::value, void>::type;
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> setStepSize() noexcept
{
step = (this->target - this->currentValue) / (FloatType) this->countdown;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> setStepSize()
{
step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / (FloatType) this->countdown);
}
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> setNextValue() noexcept
{
this->currentValue += step;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> setNextValue() noexcept
{
this->currentValue *= step;
}
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> skipCurrentValue (int numSamples) noexcept
{
this->currentValue += step * (FloatType) numSamples;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> skipCurrentValue (int numSamples)
{
this->currentValue *= (FloatType) std::pow (step, numSamples);
}
//==============================================================================
FloatType step = FloatType();
int stepsToTarget = 0;
};
template <typename FloatType>
using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
template <class SmoothedValueType>
class CommonSmoothedValueTests : public UnitTest
{
public:
CommonSmoothedValueTests()
: UnitTest ("CommonSmoothedValueTests", UnitTestCategories::smoothedValues)
{}
void runTest() override
{
beginTest ("Initial state");
{
SmoothedValueType sv;
auto value = sv.getCurrentValue();
expectEquals (sv.getTargetValue(), value);
sv.getNextValue();
expectEquals (sv.getCurrentValue(), value);
expect (! sv.isSmoothing());
}
beginTest ("Resetting");
{
auto initialValue = 15.0f;
SmoothedValueType sv (initialValue);
sv.reset (3);
expectEquals (sv.getCurrentValue(), initialValue);
auto targetValue = initialValue + 1.0f;
sv.setTargetValue (targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expectEquals (sv.getCurrentValue(), initialValue);
expect (sv.isSmoothing());
auto currentValue = sv.getNextValue();
expect (currentValue > initialValue);
expectEquals (sv.getCurrentValue(), currentValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (sv.isSmoothing());
sv.reset (5);
expectEquals (sv.getCurrentValue(), targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (! sv.isSmoothing());
sv.getNextValue();
expectEquals (sv.getCurrentValue(), targetValue);
sv.setTargetValue (1.5f);
sv.getNextValue();
float newStart = 0.2f;
sv.setCurrentAndTargetValue (newStart);
expectEquals (sv.getNextValue(), newStart);
expectEquals (sv.getTargetValue(), newStart);
expectEquals (sv.getCurrentValue(), newStart);
expect (! sv.isSmoothing());
}
beginTest ("Sample rate");
{
SmoothedValueType svSamples { 3.0f };
auto svTime = svSamples;
auto numSamples = 12;
svSamples.reset (numSamples);
svTime.reset (numSamples * 2, 1.0);
for (int i = 0; i < numSamples; ++i)
{
svTime.skip (1);
expectWithinAbsoluteError (svSamples.getNextValue(),
svTime.getNextValue(),
1.0e-7f);
}
}
beginTest ("Block processing");
{
SmoothedValueType sv (1.0f);
sv.reset (12);
sv.setTargetValue (2.0f);
const auto numSamples = 15;
AudioBuffer<float> referenceData (1, numSamples);
for (int i = 0; i < numSamples; ++i)
referenceData.setSample (0, i, sv.getNextValue());
expect (referenceData.getSample (0, 0) > 0);
expect (referenceData.getSample (0, 10) < sv.getTargetValue());
expectWithinAbsoluteError (referenceData.getSample (0, 11),
sv.getTargetValue(),
1.0e-7f);
auto getUnitData = [] (int numSamplesToGenerate)
{
AudioBuffer<float> result (1, numSamplesToGenerate);
for (int i = 0; i < numSamplesToGenerate; ++i)
result.setSample (0, i, 1.0f);
return result;
};
auto compareData = [this] (const AudioBuffer<float>& test,
const AudioBuffer<float>& reference)
{
for (int i = 0; i < test.getNumSamples(); ++i)
expectWithinAbsoluteError (test.getSample (0, i),
reference.getSample (0, i),
1.0e-7f);
};
auto testData = getUnitData (numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData.getWritePointer (0), numSamples);
compareData (testData, referenceData);
testData = getUnitData (numSamples);
AudioBuffer<float> destData (1, numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (destData.getWritePointer (0),
testData.getReadPointer (0),
numSamples);
compareData (destData, referenceData);
compareData (testData, getUnitData (numSamples));
testData = getUnitData (numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData, numSamples);
compareData (testData, referenceData);
}
beginTest ("Skip");
{
SmoothedValueType sv;
sv.reset (12);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
Array<float> reference;
for (int i = 0; i < 15; ++i)
reference.add (sv.getNextValue());
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
sv.skip (3);
expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
expectEquals (sv.skip (300), sv.getTargetValue());
expectEquals (sv.getCurrentValue(), sv.getTargetValue());
}
beginTest ("Negative");
{
SmoothedValueType sv;
auto numValues = 12;
sv.reset (numValues);
std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
{ -100.0f, -3.0f } };
for (auto range : ranges)
{
auto start = range.first, end = range.second;
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
auto val = sv.skip (numValues / 2);
if (end > start)
expect (val > start && val < end);
else
expect (val < start && val > end);
auto nextVal = sv.getNextValue();
expect (end > start ? (nextVal > val) : (nextVal < val));
auto endVal = sv.skip (500);
expectEquals (endVal, end);
expectEquals (sv.getNextValue(), end);
expectEquals (sv.getCurrentValue(), end);
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
SmoothedValueType positiveSv { -start };
positiveSv.reset (numValues);
positiveSv.setTargetValue (-end);
for (int i = 0; i < numValues + 2; ++i)
expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
}
}
}
};
#endif
} // namespace juce

+ 10033
- 0
libs/juce-current/source/juce_audio_basics/utilities/juce_WindowedSincInterpolator.cpp
File diff suppressed because it is too large
View File


+ 1170
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
File diff suppressed because it is too large
View File


+ 552
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioDeviceManager.h View File

@@ -0,0 +1,552 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages the state of some audio and midi i/o devices.
This class keeps tracks of a currently-selected audio device, through
with which it continuously streams data from an audio callback, as well as
one or more midi inputs.
The idea is that your application will create one global instance of this object,
and let it take care of creating and deleting specific types of audio devices
internally. So when the device is changed, your callbacks will just keep running
without having to worry about this.
The manager can save and reload all of its device settings as XML, which
makes it very easy for you to save and reload the audio setup of your
application.
And to make it easy to let the user change its settings, there's a component
to do just that - the AudioDeviceSelectorComponent class, which contains a set of
device selection/sample-rate/latency controls.
To use an AudioDeviceManager, create one, and use initialise() to set it up. Then
call addAudioCallback() to register your audio callback with it, and use that to process
your audio data.
The manager also acts as a handy hub for incoming midi messages, allowing a
listener to register for messages from either a specific midi device, or from whatever
the current default midi input device is. The listener then doesn't have to worry about
re-registering with different midi devices if they are changed or deleted.
And yet another neat trick is that amount of CPU time being used is measured and
available with the getCpuUsage() method.
The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to
listeners whenever one of its settings is changed.
@see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType
@tags{Audio}
*/
class JUCE_API AudioDeviceManager : public ChangeBroadcaster
{
public:
//==============================================================================
/** Creates a default AudioDeviceManager.
Initially no audio device will be selected. You should call the initialise() method
and register an audio callback with setAudioCallback() before it'll be able to
actually make any noise.
*/
AudioDeviceManager();
/** Destructor. */
~AudioDeviceManager() override;
//==============================================================================
/**
This structure holds a set of properties describing the current audio setup.
An AudioDeviceManager uses this class to save/load its current settings, and to
specify your preferred options when opening a device.
@see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise()
*/
struct JUCE_API AudioDeviceSetup
{
/** The name of the audio device used for output.
The name has to be one of the ones listed by the AudioDeviceManager's currently
selected device type.
This may be the same as the input device.
An empty string indicates the default device.
*/
String outputDeviceName;
/** The name of the audio device used for input.
This may be the same as the output device.
An empty string indicates the default device.
*/
String inputDeviceName;
/** The current sample rate.
This rate is used for both the input and output devices.
A value of 0 indicates that you don't care what rate is used, and the
device will choose a sensible rate for you.
*/
double sampleRate = 0;
/** The buffer size, in samples.
This buffer size is used for both the input and output devices.
A value of 0 indicates the default buffer size.
*/
int bufferSize = 0;
/** The set of active input channels.
The bits that are set in this array indicate the channels of the
input device that are active.
If useDefaultInputChannels is true, this value is ignored.
*/
BigInteger inputChannels;
/** If this is true, it indicates that the inputChannels array
should be ignored, and instead, the device's default channels
should be used.
*/
bool useDefaultInputChannels = true;
/** The set of active output channels.
The bits that are set in this array indicate the channels of the
input device that are active.
If useDefaultOutputChannels is true, this value is ignored.
*/
BigInteger outputChannels;
/** If this is true, it indicates that the outputChannels array
should be ignored, and instead, the device's default channels
should be used.
*/
bool useDefaultOutputChannels = true;
bool operator== (const AudioDeviceSetup&) const;
bool operator!= (const AudioDeviceSetup&) const;
};
//==============================================================================
/** Opens a set of audio devices ready for use.
This will attempt to open either a default audio device, or one that was
previously saved as XML.
@param numInputChannelsNeeded the maximum number of input channels your app would like to
use (the actual number of channels opened may be less than
the number requested)
@param numOutputChannelsNeeded the maximum number of output channels your app would like to
use (the actual number of channels opened may be less than
the number requested)
@param savedState either a previously-saved state that was produced
by createStateXml(), or nullptr if you want the manager
to choose the best device to open.
@param selectDefaultDeviceOnFailure if true, then if the device specified in the XML
fails to open, then a default device will be used
instead. If false, then on failure, no device is
opened.
@param preferredDefaultDeviceName if this is not empty, and there's a device with this
name, then that will be used as the default device
(assuming that there wasn't one specified in the XML).
The string can actually be a simple wildcard, containing "*"
and "?" characters
@param preferredSetupOptions if this is non-null, the structure will be used as the
set of preferred settings when opening the device. If you
use this parameter, the preferredDefaultDeviceName
field will be ignored
@returns an error message if anything went wrong, or an empty string if it worked ok.
*/
String initialise (int numInputChannelsNeeded,
int numOutputChannelsNeeded,
const XmlElement* savedState,
bool selectDefaultDeviceOnFailure,
const String& preferredDefaultDeviceName = String(),
const AudioDeviceSetup* preferredSetupOptions = nullptr);
/** Resets everything to a default device setup, clearing any stored settings. */
String initialiseWithDefaultDevices (int numInputChannelsNeeded,
int numOutputChannelsNeeded);
/** Returns some XML representing the current state of the manager.
This stores the current device, its samplerate, block size, etc, and
can be restored later with initialise().
Note that this can return a null pointer if no settings have been explicitly changed
(i.e. if the device manager has just been left in its default state).
*/
std::unique_ptr<XmlElement> createStateXml() const;
//==============================================================================
/** Returns the current device properties that are in use.
@see setAudioDeviceSetup
*/
AudioDeviceSetup getAudioDeviceSetup() const;
/** Returns the current device properties that are in use.
This is an old method, kept around for compatibility, but you should prefer the new
version which returns the result rather than taking an out-parameter.
@see getAudioDeviceSetup()
*/
void getAudioDeviceSetup (AudioDeviceSetup& result) const;
/** Changes the current device or its settings.
If you want to change a device property, like the current sample rate or
block size, you can call getAudioDeviceSetup() to retrieve the current
settings, then tweak the appropriate fields in the AudioDeviceSetup structure,
and pass it back into this method to apply the new settings.
@param newSetup the settings that you'd like to use
@param treatAsChosenDevice if this is true and if the device opens correctly, these new
settings will be taken as having been explicitly chosen by the
user, and the next time createStateXml() is called, these settings
will be returned. If it's false, then the device is treated as a
temporary or default device, and a call to createStateXml() will
return either the last settings that were made with treatAsChosenDevice
as true, or the last XML settings that were passed into initialise().
@returns an error message if anything went wrong, or an empty string if it worked ok.
@see getAudioDeviceSetup
*/
String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, bool treatAsChosenDevice);
/** Returns the currently-active audio device. */
AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice.get(); }
/** Returns the type of audio device currently in use.
@see setCurrentAudioDeviceType
*/
String getCurrentAudioDeviceType() const { return currentDeviceType; }
/** Returns the currently active audio device type object.
Don't keep a copy of this pointer - it's owned by the device manager and could
change at any time.
*/
AudioIODeviceType* getCurrentDeviceTypeObject() const;
/** Changes the class of audio device being used.
This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call
this because there's only one type: CoreAudio.
For a list of types, see getAvailableDeviceTypes().
*/
void setCurrentAudioDeviceType (const String& type, bool treatAsChosenDevice);
/** Closes the currently-open device.
You can call restartLastAudioDevice() later to reopen it in the same state
that it was just in.
*/
void closeAudioDevice();
/** Tries to reload the last audio device that was running.
Note that this only reloads the last device that was running before
closeAudioDevice() was called - it doesn't reload any kind of saved-state,
and can only be called after a device has been opened with SetAudioDevice().
If a device is already open, this call will do nothing.
*/
void restartLastAudioDevice();
//==============================================================================
/** Registers an audio callback to be used.
The manager will redirect callbacks from whatever audio device is currently
in use to all registered callback objects. If more than one callback is
active, they will all be given the same input data, and their outputs will
be summed.
If necessary, this method will invoke audioDeviceAboutToStart() on the callback
object before returning.
To remove a callback, use removeAudioCallback().
*/
void addAudioCallback (AudioIODeviceCallback* newCallback);
/** Deregisters a previously added callback.
If necessary, this method will invoke audioDeviceStopped() on the callback
object before returning.
@see addAudioCallback
*/
void removeAudioCallback (AudioIODeviceCallback* callback);
//==============================================================================
/** Returns the average proportion of available CPU being spent inside the audio callbacks.
@returns A value between 0 and 1.0 to indicate the approximate proportion of CPU
time spent in the callbacks.
*/
double getCpuUsage() const;
//==============================================================================
/** Enables or disables a midi input device.
The list of devices can be obtained with the MidiInput::getAvailableDevices() method.
Any incoming messages from enabled input devices will be forwarded on to all the
listeners that have been registered with the addMidiInputDeviceCallback() method. They
can either register for messages from a particular device, or from just the "default"
midi input.
Routing the midi input via an AudioDeviceManager means that when a listener
registers for the default midi input, this default device can be changed by the
manager without the listeners having to know about it or re-register.
It also means that a listener can stay registered for a midi input that is disabled
or not present, so that when the input is re-enabled, the listener will start
receiving messages again.
@see addMidiInputDeviceCallback, isMidiInputDeviceEnabled
*/
void setMidiInputDeviceEnabled (const String& deviceIdentifier, bool enabled);
/** Returns true if a given midi input device is being used.
@see setMidiInputDeviceEnabled
*/
bool isMidiInputDeviceEnabled (const String& deviceIdentifier) const;
/** Registers a listener for callbacks when midi events arrive from a midi input.
The device identifier can be empty to indicate that it wants to receive all incoming
events from all the enabled MIDI inputs. Or it can be the identifier of one of the
MIDI input devices if it just wants the events from that device. (see
MidiInput::getAvailableDevices() for the list of devices).
Only devices which are enabled (see the setMidiInputDeviceEnabled() method) will have their
events forwarded on to listeners.
*/
void addMidiInputDeviceCallback (const String& deviceIdentifier,
MidiInputCallback* callback);
/** Removes a listener that was previously registered with addMidiInputDeviceCallback(). */
void removeMidiInputDeviceCallback (const String& deviceIdentifier,
MidiInputCallback* callback);
//==============================================================================
/** Sets a midi output device to use as the default.
The list of devices can be obtained with the MidiOutput::getAvailableDevices() method.
The specified device will be opened automatically and can be retrieved with the
getDefaultMidiOutput() method.
Pass in an empty string to deselect all devices. For the default device, you
can use MidiOutput::getDefaultDevice().
@see getDefaultMidiOutput, getDefaultMidiOutputIdentifier
*/
void setDefaultMidiOutputDevice (const String& deviceIdentifier);
/** Returns the name of the default midi output.
@see setDefaultMidiOutputDevice, getDefaultMidiOutput
*/
const String& getDefaultMidiOutputIdentifier() const noexcept { return defaultMidiOutputDeviceInfo.identifier; }
/** Returns the current default midi output device. If no device has been selected, or the
device can't be opened, this will return nullptr.
@see getDefaultMidiOutputIdentifier
*/
MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput.get(); }
//==============================================================================
/** Returns a list of the types of device supported. */
const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes();
/** Creates a list of available types.
This will add a set of new AudioIODeviceType objects to the specified list, to
represent each available types of device.
You can override this if your app needs to do something specific, like avoid
using DirectSound devices, etc.
*/
virtual void createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& types);
/** Adds a new device type to the list of types. */
void addAudioDeviceType (std::unique_ptr<AudioIODeviceType> newDeviceType);
/** Removes a previously added device type from the manager. */
void removeAudioDeviceType (AudioIODeviceType* deviceTypeToRemove);
//==============================================================================
/** Plays a beep through the current audio device.
This is here to allow the audio setup UI panels to easily include a "test"
button so that the user can check where the audio is coming from.
*/
void playTestSound();
//==============================================================================
/**
A simple reference-counted struct that holds a level-meter value that can be read
using getCurrentLevel().
This is used to ensure that the level processing code is only executed when something
holds a reference to one of these objects and will be bypassed otherwise.
@see getInputLevelGetter, getOutputLevelGetter
*/
struct LevelMeter : public ReferenceCountedObject
{
LevelMeter() noexcept;
double getCurrentLevel() const noexcept;
using Ptr = ReferenceCountedObjectPtr<LevelMeter>;
private:
friend class AudioDeviceManager;
Atomic<float> level { 0 };
void updateLevel (const float* const*, int numChannels, int numSamples) noexcept;
};
/** Returns a reference-counted object that can be used to get the current input level.
You need to store this object locally to ensure that the reference count is incremented
and decremented properly. The current input level value can be read using getCurrentLevel().
*/
LevelMeter::Ptr getInputLevelGetter() noexcept { return inputLevelGetter; }
/** Returns a reference-counted object that can be used to get the current output level.
You need to store this object locally to ensure that the reference count is incremented
and decremented properly. The current output level value can be read using getCurrentLevel().
*/
LevelMeter::Ptr getOutputLevelGetter() noexcept { return outputLevelGetter; }
//==============================================================================
/** Returns the a lock that can be used to synchronise access to the audio callback.
Obviously while this is locked, you're blocking the audio thread from running, so
it must only be used for very brief periods when absolutely necessary.
*/
CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; }
/** Returns the a lock that can be used to synchronise access to the midi callback.
Obviously while this is locked, you're blocking the midi system from running, so
it must only be used for very brief periods when absolutely necessary.
*/
CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; }
//==============================================================================
/** Returns the number of under- or over runs reported.
This method will use the underlying device's native getXRunCount if it supports
it. Otherwise it will estimate the number of under-/overruns by measuring the
time it spent in the audio callback.
*/
int getXRunCount() const noexcept;
//==============================================================================
/** Deprecated. */
void setMidiInputEnabled (const String&, bool);
/** Deprecated. */
bool isMidiInputEnabled (const String&) const;
/** Deprecated. */
void addMidiInputCallback (const String&, MidiInputCallback*);
/** Deprecated. */
void removeMidiInputCallback (const String&, MidiInputCallback*);
/** Deprecated. */
void setDefaultMidiOutput (const String&);
/** Deprecated. */
const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputDeviceInfo.name; }
private:
//==============================================================================
OwnedArray<AudioIODeviceType> availableDeviceTypes;
OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs;
AudioDeviceSetup currentSetup;
std::unique_ptr<AudioIODevice> currentAudioDevice;
Array<AudioIODeviceCallback*> callbacks;
int numInputChansNeeded = 0, numOutputChansNeeded = 2;
String preferredDeviceName, currentDeviceType;
std::unique_ptr<XmlElement> lastExplicitSettings;
mutable bool listNeedsScanning = true;
AudioBuffer<float> tempBuffer;
struct MidiCallbackInfo
{
String deviceIdentifier;
MidiInputCallback* callback;
};
Array<MidiDeviceInfo> midiDeviceInfosFromXml;
std::vector<std::unique_ptr<MidiInput>> enabledMidiInputs;
Array<MidiCallbackInfo> midiCallbacks;
MidiDeviceInfo defaultMidiOutputDeviceInfo;
std::unique_ptr<MidiOutput> defaultMidiOutput;
CriticalSection audioCallbackLock, midiCallbackLock;
std::unique_ptr<AudioBuffer<float>> testSound;
int testSoundPosition = 0;
AudioProcessLoadMeasurer loadMeasurer;
LevelMeter::Ptr inputLevelGetter { new LevelMeter() },
outputLevelGetter { new LevelMeter() };
//==============================================================================
class CallbackHandler;
std::unique_ptr<CallbackHandler> callbackHandler;
void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels,
float** outputChannelData, int totalNumOutputChannels, int numSamples);
void audioDeviceAboutToStartInt (AudioIODevice*);
void audioDeviceStoppedInt();
void audioDeviceErrorInt (const String&);
void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&);
void audioDeviceListChanged();
String restartDevice (int blockSizeToUse, double sampleRateToUse,
const BigInteger& ins, const BigInteger& outs);
void stopDevice();
void updateXml();
void createDeviceTypesIfNeeded();
void scanDevicesIfNeeded();
void deleteCurrentDevice();
double chooseBestSampleRate (double preferred) const;
int chooseBestBufferSize (int preferred) const;
void insertDefaultDeviceNames (AudioDeviceSetup&) const;
String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure,
const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
AudioIODeviceType* findType (const String& inputName, const String& outputName);
AudioIODeviceType* findType (const String& typeName);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager)
};
} // namespace juce

+ 45
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODevice.cpp View File

@@ -0,0 +1,45 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName)
: name (deviceName), typeName (deviceTypeName)
{
}
AudioIODevice::~AudioIODevice() {}
void AudioIODeviceCallback::audioDeviceError (const String&) {}
bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; }
bool AudioIODevice::hasControlPanel() const { return false; }
int AudioIODevice::getXRunCount() const noexcept { return -1; }
bool AudioIODevice::showControlPanel()
{
jassertfalse; // this should only be called for devices which return true from
// their hasControlPanel() method.
return false;
}
} // namespace juce

+ 325
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODevice.h View File

@@ -0,0 +1,325 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class AudioIODevice;
//==============================================================================
/**
One of these is passed to an AudioIODevice object to stream the audio data
in and out.
The AudioIODevice will repeatedly call this class's audioDeviceIOCallback()
method on its own high-priority audio thread, when it needs to send or receive
the next block of data.
@see AudioIODevice, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODeviceCallback
{
public:
/** Destructor. */
virtual ~AudioIODeviceCallback() = default;
/** Processes a block of incoming and outgoing audio data.
The subclass's implementation should use the incoming audio for whatever
purposes it needs to, and must fill all the output channels with the next
block of output data before returning.
The channel data is arranged with the same array indices as the channel name
array returned by AudioIODevice::getOutputChannelNames(), but those channels
that aren't specified in AudioIODevice::open() will have a null pointer for their
associated channel, so remember to check for this.
@param inputChannelData a set of arrays containing the audio data for each
incoming channel - this data is valid until the function
returns. There will be one channel of data for each input
channel that was enabled when the audio device was opened
(see AudioIODevice::open())
@param numInputChannels the number of pointers to channel data in the
inputChannelData array.
@param outputChannelData a set of arrays which need to be filled with the data
that should be sent to each outgoing channel of the device.
There will be one channel of data for each output channel
that was enabled when the audio device was opened (see
AudioIODevice::open())
The initial contents of the array is undefined, so the
callback function must fill all the channels with zeros if
its output is silence. Failing to do this could cause quite
an unpleasant noise!
@param numOutputChannels the number of pointers to channel data in the
outputChannelData array.
@param numSamples the number of samples in each channel of the input and
output arrays. The number of samples will depend on the
audio device's buffer size and will usually remain constant,
although this isn't guaranteed. For example, on Android,
on devices which support it, Android will chop up your audio
processing into several smaller callbacks to ensure higher audio
performance. So make sure your code can cope with reasonable
changes in the buffer size from one callback to the next.
*/
virtual void audioDeviceIOCallback (const float** inputChannelData,
int numInputChannels,
float** outputChannelData,
int numOutputChannels,
int numSamples) = 0;
/** Called to indicate that the device is about to start calling back.
This will be called just before the audio callbacks begin, either when this
callback has just been added to an audio device, or after the device has been
restarted because of a sample-rate or block-size change.
You can use this opportunity to find out the sample rate and block size
that the device is going to use by calling the AudioIODevice::getCurrentSampleRate()
and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer.
@param device the audio IO device that will be used to drive the callback.
Note that if you're going to store this this pointer, it is
only valid until the next time that audioDeviceStopped is called.
*/
virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0;
/** Called to indicate that the device has stopped. */
virtual void audioDeviceStopped() = 0;
/** This can be overridden to be told if the device generates an error while operating.
Be aware that this could be called by any thread! And not all devices perform
this callback.
*/
virtual void audioDeviceError (const String& errorMessage);
};
//==============================================================================
/**
Base class for an audio device with synchronised input and output channels.
Subclasses of this are used to implement different protocols such as DirectSound,
ASIO, CoreAudio, etc.
To create one of these, you'll need to use the AudioIODeviceType class - see the
documentation for that class for more info.
For an easier way of managing audio devices and their settings, have a look at the
AudioDeviceManager class.
@see AudioIODeviceType, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODevice
{
public:
/** Destructor. */
virtual ~AudioIODevice();
//==============================================================================
/** Returns the device's name, (as set in the constructor). */
const String& getName() const noexcept { return name; }
/** Returns the type of the device.
E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it.
*/
const String& getTypeName() const noexcept { return typeName; }
//==============================================================================
/** Returns the names of all the available output channels on this device.
To find out which of these are currently in use, call getActiveOutputChannels().
*/
virtual StringArray getOutputChannelNames() = 0;
/** Returns the names of all the available input channels on this device.
To find out which of these are currently in use, call getActiveInputChannels().
*/
virtual StringArray getInputChannelNames() = 0;
//==============================================================================
/** Returns the set of sample-rates this device supports.
@see getCurrentSampleRate
*/
virtual Array<double> getAvailableSampleRates() = 0;
/** Returns the set of buffer sizes that are available.
@see getCurrentBufferSizeSamples, getDefaultBufferSize
*/
virtual Array<int> getAvailableBufferSizes() = 0;
/** Returns the default buffer-size to use.
@returns a number of samples
@see getAvailableBufferSizes
*/
virtual int getDefaultBufferSize() = 0;
//==============================================================================
/** Tries to open the device ready to play.
@param inputChannels a BigInteger in which a set bit indicates that the corresponding
input channel should be enabled
@param outputChannels a BigInteger in which a set bit indicates that the corresponding
output channel should be enabled
@param sampleRate the sample rate to try to use - to find out which rates are
available, see getAvailableSampleRates()
@param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer
sizes, see getAvailableBufferSizes()
@returns an error description if there's a problem, or an empty string if it succeeds in
opening the device
@see close
*/
virtual String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double sampleRate,
int bufferSizeSamples) = 0;
/** Closes and releases the device if it's open. */
virtual void close() = 0;
/** Returns true if the device is still open.
A device might spontaneously close itself if something goes wrong, so this checks if
it's still open.
*/
virtual bool isOpen() = 0;
/** Starts the device actually playing.
This must be called after the device has been opened.
@param callback the callback to use for streaming the data.
@see AudioIODeviceCallback, open
*/
virtual void start (AudioIODeviceCallback* callback) = 0;
/** Stops the device playing.
Once a device has been started, this will stop it. Any pending calls to the
callback class will be flushed before this method returns.
*/
virtual void stop() = 0;
/** Returns true if the device is still calling back.
The device might mysteriously stop, so this checks whether it's
still playing.
*/
virtual bool isPlaying() = 0;
/** Returns the last error that happened if anything went wrong. */
virtual String getLastError() = 0;
//==============================================================================
/** Returns the buffer size that the device is currently using.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual int getCurrentBufferSizeSamples() = 0;
/** Returns the sample rate that the device is currently using.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual double getCurrentSampleRate() = 0;
/** Returns the device's current physical bit-depth.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual int getCurrentBitDepth() = 0;
/** Returns a mask showing which of the available output channels are currently
enabled.
@see getOutputChannelNames
*/
virtual BigInteger getActiveOutputChannels() const = 0;
/** Returns a mask showing which of the available input channels are currently
enabled.
@see getInputChannelNames
*/
virtual BigInteger getActiveInputChannels() const = 0;
/** Returns the device's output latency.
This is the delay in samples between a callback getting a block of data, and
that data actually getting played.
*/
virtual int getOutputLatencyInSamples() = 0;
/** Returns the device's input latency.
This is the delay in samples between some audio actually arriving at the soundcard,
and the callback getting passed this block of data.
*/
virtual int getInputLatencyInSamples() = 0;
//==============================================================================
/** True if this device can show a pop-up control panel for editing its settings.
This is generally just true of ASIO devices. If true, you can call showControlPanel()
to display it.
*/
virtual bool hasControlPanel() const;
/** Shows a device-specific control panel if there is one.
This should only be called for devices which return true from hasControlPanel().
*/
virtual bool showControlPanel();
/** On devices which support it, this allows automatic gain control or other
mic processing to be disabled.
If the device doesn't support this operation, it'll return false.
*/
virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled);
//==============================================================================
/** Returns the number of under- or over runs reported by the OS since
playback/recording has started.
This number may be different than determining the Xrun count manually (by
measuring the time spent in the audio callback) as the OS may be doing
some buffering internally - especially on mobile devices.
Returns -1 if playback/recording has not started yet or if getting the underrun
count is not supported for this device (Android SDK 23 and lower).
*/
virtual int getXRunCount() const noexcept;
//==============================================================================
protected:
/** Creates a device, setting its name and type member variables. */
AudioIODevice (const String& deviceName,
const String& typeName);
/** @internal */
String name, typeName;
};
} // namespace juce

+ 89
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp View File

@@ -0,0 +1,89 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AudioIODeviceType::AudioIODeviceType (const String& name)
: typeName (name)
{
}
AudioIODeviceType::~AudioIODeviceType()
{
}
//==============================================================================
void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); }
void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); }
void AudioIODeviceType::callDeviceChangeListeners()
{
listeners.call ([] (Listener& l) { l.audioDeviceListChanged(); });
}
//==============================================================================
#if ! JUCE_MAC
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; }
#endif
#if ! JUCE_IOS
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_WASAPI)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_ASIO)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_ALSA)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_JACK)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_BELA)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; }
#endif
#if ! JUCE_ANDROID
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; }
#endif
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; }
#endif
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OBOE)
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; }
#endif
} // namespace juce

+ 184
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_AudioIODeviceType.h View File

@@ -0,0 +1,184 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc.
To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes()
method. Each of the objects returned can then be used to list the available
devices of that type. E.g.
@code
OwnedArray<AudioIODeviceType> types;
myAudioDeviceManager.createAudioDeviceTypes (types);
for (int i = 0; i < types.size(); ++i)
{
String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc.
types[i]->scanForDevices(); // This must be called before getting the list of devices
StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type
for (int j = 0; j < deviceNames.size(); ++j)
{
AudioIODevice* device = types[i]->createDevice (deviceNames [j]);
...
}
}
@endcode
For an easier way of managing audio devices and their settings, have a look at the
AudioDeviceManager class.
@see AudioIODevice, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODeviceType
{
public:
//==============================================================================
/** Returns the name of this type of driver that this object manages.
This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc.
*/
const String& getTypeName() const noexcept { return typeName; }
//==============================================================================
/** Refreshes the object's cached list of known devices.
This must be called at least once before calling getDeviceNames() or any of
the other device creation methods.
*/
virtual void scanForDevices() = 0;
/** Returns the list of available devices of this type.
The scanForDevices() method must have been called to create this list.
@param wantInputNames only really used by DirectSound where devices are split up
into inputs and outputs, this indicates whether to use
the input or output name to refer to a pair of devices.
*/
virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0;
/** Returns the name of the default device.
This will be one of the names from the getDeviceNames() list.
@param forInput if true, this means that a default input device should be
returned; if false, it should return the default output
*/
virtual int getDefaultDeviceIndex (bool forInput) const = 0;
/** Returns the index of a given device in the list of device names.
If asInput is true, it shows the index in the inputs list, otherwise it
looks for it in the outputs list.
*/
virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0;
/** Returns true if two different devices can be used for the input and output.
*/
virtual bool hasSeparateInputsAndOutputs() const = 0;
/** Creates one of the devices of this type.
The deviceName must be one of the strings returned by getDeviceNames(), and
scanForDevices() must have been called before this method is used.
*/
virtual AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName) = 0;
//==============================================================================
/**
A class for receiving events when audio devices are inserted or removed.
You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object
using the AudioIODeviceType::addListener() method, and it will be called when
devices of that type are added or removed.
@see AudioIODeviceType::addListener, AudioIODeviceType::removeListener
*/
class Listener
{
public:
virtual ~Listener() = default;
/** Called when the list of available audio devices changes. */
virtual void audioDeviceListChanged() = 0;
};
/** Adds a listener that will be called when this type of device is added or
removed from the system.
*/
void addListener (Listener* listener);
/** Removes a listener that was previously added with addListener(). */
void removeListener (Listener* listener);
//==============================================================================
/** Destructor. */
virtual ~AudioIODeviceType();
//==============================================================================
/** Creates a CoreAudio device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_CoreAudio();
/** Creates an iOS device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_iOSAudio();
/** Creates a WASAPI device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode);
/** Creates a DirectSound device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_DirectSound();
/** Creates an ASIO device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_ASIO();
/** Creates an ALSA device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_ALSA();
/** Creates a JACK device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_JACK();
/** Creates an Android device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Android();
/** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_OpenSLES();
/** Creates an Oboe device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Oboe();
/** Creates a Bela device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Bela();
protected:
explicit AudioIODeviceType (const String& typeName);
/** Synchronously calls all the registered device list change listeners. */
void callDeviceChangeListeners();
private:
String typeName;
ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType)
};
} // namespace juce

+ 59
- 0
libs/juce-current/source/juce_audio_devices/audio_io/juce_SystemAudioVolume.h View File

@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Contains functions to control the system's master volume.
@tags{Audio}
*/
class JUCE_API SystemAudioVolume
{
public:
//==============================================================================
/** Returns the operating system's current volume level in the range 0 to 1.0 */
static float JUCE_CALLTYPE getGain();
/** Attempts to set the operating system's current volume level.
@param newGain the level, between 0 and 1.0
@returns true if the operation succeeds
*/
static bool JUCE_CALLTYPE setGain (float newGain);
/** Returns true if the system's audio output is currently muted. */
static bool JUCE_CALLTYPE isMuted();
/** Attempts to mute the operating system's audio output.
@param shouldBeMuted true if you want it to be muted
@returns true if the operation succeeds
*/
static bool JUCE_CALLTYPE setMuted (bool shouldBeMuted);
private:
SystemAudioVolume(); // Don't instantiate this class, just call its static fns.
JUCE_DECLARE_NON_COPYABLE (SystemAudioVolume)
};
} // namespace juce

+ 261
- 0
libs/juce-current/source/juce_audio_devices/juce_audio_devices.cpp View File

@@ -0,0 +1,261 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#ifdef JUCE_AUDIO_DEVICES_H_INCLUDED
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
#ifndef JUCE_USE_WINRT_MIDI
#define JUCE_USE_WINRT_MIDI 0
#endif
#if JUCE_USE_WINRT_MIDI
#define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1
#endif
#include "juce_audio_devices.h"
//==============================================================================
#if JUCE_MAC
#define Point CarbonDummyPointName
#define Component CarbonDummyCompName
#import <CoreAudio/AudioHardware.h>
#import <CoreMIDI/MIDIServices.h>
#import <AudioToolbox/AudioServices.h>
#undef Point
#undef Component
#elif JUCE_IOS
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMIDI/MIDIServices.h>
#if TARGET_OS_SIMULATOR
#import <CoreMIDI/MIDINetworkSession.h>
#endif
//==============================================================================
#elif JUCE_WINDOWS
#if JUCE_WASAPI
#include <mmreg.h>
#endif
#if JUCE_USE_WINRT_MIDI && JUCE_MSVC
/* If you cannot find any of the header files below then you are probably
attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you
need to install version 10.0.14393.0 of the Windows Standalone SDK and you may
need to add the path to the WinRT headers to your build system. This path should
have the form "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
Also please note that Microsoft's Bluetooth MIDI stack has multiple issues, so
this API is EXPERIMENTAL - use at your own risk!
*/
#include <windows.devices.h>
#include <windows.devices.midi.h>
#include <windows.devices.enumeration.h>
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265)
#include <wrl/event.h>
JUCE_END_IGNORE_WARNINGS_MSVC
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4467)
#include <robuffer.h>
JUCE_END_IGNORE_WARNINGS_MSVC
#endif
#if JUCE_ASIO
/* This is very frustrating - we only need to use a handful of definitions from
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy
about 30 lines of code into this cpp file to create a fully stand-alone ASIO
implementation...
..unfortunately that would break Steinberg's license agreement for use of
their SDK, so I'm not allowed to do this.
This means that anyone who wants to use JUCE's ASIO abilities will have to:
1) Agree to Steinberg's licensing terms and download the ASIO SDK
(see http://www.steinberg.net/en/company/developers.html).
2) Enable this code with a global definition #define JUCE_ASIO 1.
3) Make sure that your header search path contains the iasiodrv.h file that
comes with the SDK. (Only about a handful of the SDK header files are actually
needed - so to simplify things, you could just copy these into your JUCE directory).
*/
#include <iasiodrv.h>
#endif
//==============================================================================
#elif JUCE_LINUX
#if JUCE_ALSA
/* Got an include error here? If so, you've either not got ALSA installed, or you've
not got your paths set up correctly to find its header files.
The package you need to install to get ASLA support is "libasound2-dev".
If you don't have the ALSA library and don't want to build JUCE with audio support,
just set the JUCE_ALSA flag to 0.
*/
#include <alsa/asoundlib.h>
#endif
#if JUCE_JACK
/* Got an include error here? If so, you've either not got jack-audio-connection-kit
installed, or you've not got your paths set up correctly to find its header files.
The package you need to install to get JACK support is "libjack-dev".
If you don't have the jack-audio-connection-kit library and don't want to build
JUCE with low latency audio support, just set the JUCE_JACK flag to 0.
*/
#include <jack/jack.h>
#endif
#if JUCE_BELA
/* Got an include error here? If so, you've either not got the bela headers
installed, or you've not got your paths set up correctly to find its header
files.
*/
#include <Bela.h>
#include <Midi.h>
#endif
#undef SIZEOF
//==============================================================================
#elif JUCE_ANDROID
#if JUCE_USE_ANDROID_OPENSLES
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
#endif
#if JUCE_USE_ANDROID_OBOE
#if JUCE_USE_ANDROID_OPENSLES
#error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES"
#endif
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter",
"-Wzero-as-null-pointer-constant",
"-Winconsistent-missing-destructor-override",
"-Wshadow-field-in-constructor",
"-Wshadow-field")
#include <oboe/Oboe.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
#endif
#include "audio_io/juce_AudioDeviceManager.cpp"
#include "audio_io/juce_AudioIODevice.cpp"
#include "audio_io/juce_AudioIODeviceType.cpp"
#include "midi_io/juce_MidiMessageCollector.cpp"
#include "midi_io/juce_MidiDevices.cpp"
#include "sources/juce_AudioSourcePlayer.cpp"
#include "sources/juce_AudioTransportSource.cpp"
#include "native/juce_MidiDataConcatenator.h"
//==============================================================================
#if JUCE_MAC
#include "native/juce_mac_CoreAudio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
//==============================================================================
#elif JUCE_IOS
#include "native/juce_ios_Audio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
//==============================================================================
#elif JUCE_WINDOWS
#if JUCE_WASAPI
#include "native/juce_win32_WASAPI.cpp"
#endif
#if JUCE_DIRECTSOUND
#include "native/juce_win32_DirectSound.cpp"
#endif
#include "native/juce_win32_Midi.cpp"
#if JUCE_ASIO
#include "native/juce_win32_ASIO.cpp"
#endif
//==============================================================================
#elif JUCE_LINUX
#if JUCE_ALSA
#include "native/juce_linux_ALSA.cpp"
#endif
#if JUCE_JACK
#include "native/juce_linux_JackAudio.cpp"
#endif
#if JUCE_BELA
#include "native/juce_linux_Bela.cpp"
#else
#include "native/juce_linux_Midi.cpp"
#endif
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_android_Audio.cpp"
#include "native/juce_android_Midi.cpp"
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE
#include "native/juce_android_HighPerformanceAudioHelpers.h"
#if JUCE_USE_ANDROID_OPENSLES
#include "native/juce_android_OpenSL.cpp"
#endif
#if JUCE_USE_ANDROID_OBOE
#include "native/juce_android_Oboe.cpp"
#endif
#endif
#endif
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
namespace juce
{
// None of these methods are available. (On Windows you might need to enable WASAPI for this)
float JUCE_CALLTYPE SystemAudioVolume::getGain() { jassertfalse; return 0.0f; }
bool JUCE_CALLTYPE SystemAudioVolume::setGain (float) { jassertfalse; return false; }
bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { jassertfalse; return false; }
bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; }
}
#endif

+ 186
- 0
libs/juce-current/source/juce_audio_devices/juce_audio_devices.h View File

@@ -0,0 +1,186 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_audio_devices
vendor: juce
version: 6.0.0
name: JUCE audio and MIDI I/O device classes
description: Classes to play and record from audio and MIDI I/O devices
website: http://www.juce.com/juce
license: ISC
dependencies: juce_audio_basics, juce_events
OSXFrameworks: CoreAudio CoreMIDI AudioToolbox
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation
linuxPackages: alsa
mingwLibs: winmm
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_AUDIO_DEVICES_H_INCLUDED
#include <juce_events/juce_events.h>
#include <juce_audio_basics/juce_audio_basics.h>
#if JUCE_MODULE_AVAILABLE_juce_graphics
#include <juce_graphics/juce_graphics.h>
#endif
//==============================================================================
/** Config: JUCE_USE_WINRT_MIDI
Enables the use of the Windows Runtime API for MIDI, allowing connections
to Bluetooth Low Energy devices on Windows 10 version 1809 (October 2018
Update) and later. If you enable this flag then older versions of Windows
will automatically fall back to using the regular Win32 MIDI API.
You will need version 10.0.14393.0 of the Windows Standalone SDK to compile
and you may need to add the path to the WinRT headers. The path to the
headers will be something similar to
"C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
*/
#ifndef JUCE_USE_WINRT_MIDI
#define JUCE_USE_WINRT_MIDI 0
#endif
/** Config: JUCE_ASIO
Enables ASIO audio devices (MS Windows only).
Turning this on means that you'll need to have the Steinberg ASIO SDK installed
on your Windows build machine.
See the comments in the ASIOAudioIODevice class's header file for more
info about this.
*/
#ifndef JUCE_ASIO
#define JUCE_ASIO 0
#endif
/** Config: JUCE_WASAPI
Enables WASAPI audio devices (Windows Vista and above). See also the
JUCE_WASAPI_EXCLUSIVE flag.
*/
#ifndef JUCE_WASAPI
#define JUCE_WASAPI 1
#endif
/** Config: JUCE_WASAPI_EXCLUSIVE
Enables WASAPI audio devices in exclusive mode (Windows Vista and above).
*/
#ifndef JUCE_WASAPI_EXCLUSIVE
#define JUCE_WASAPI_EXCLUSIVE 0
#endif
/** Config: JUCE_DIRECTSOUND
Enables DirectSound audio (MS Windows only).
*/
#ifndef JUCE_DIRECTSOUND
#define JUCE_DIRECTSOUND 1
#endif
/** Config: JUCE_ALSA
Enables ALSA audio devices (Linux only).
*/
#ifndef JUCE_ALSA
#define JUCE_ALSA 1
#endif
/** Config: JUCE_JACK
Enables JACK audio devices (Linux only).
*/
#ifndef JUCE_JACK
#define JUCE_JACK 0
#endif
/** Config: JUCE_BELA
Enables Bela audio devices on Bela boards.
*/
#ifndef JUCE_BELA
#define JUCE_BELA 0
#endif
/** Config: JUCE_USE_ANDROID_OBOE
Enables Oboe devices (Android only, API 16 or above).
*/
#ifndef JUCE_USE_ANDROID_OBOE
#define JUCE_USE_ANDROID_OBOE 1
#endif
#if JUCE_USE_ANDROID_OBOE && JUCE_ANDROID_API_VERSION < 16
#undef JUCE_USE_ANDROID_OBOE
#define JUCE_USE_ANDROID_OBOE 0
#endif
/** Config: JUCE_USE_OBOE_STABILIZED_CALLBACK
If JUCE_USE_ANDROID_OBOE is enabled, enabling this will wrap output audio
streams in the oboe::StabilizedCallback class. This class attempts to keep
the CPU spinning to avoid it being scaled down on certain devices.
*/
#ifndef JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
#define JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK 0
#endif
/** Config: JUCE_USE_ANDROID_OPENSLES
Enables OpenSLES devices (Android only).
*/
#ifndef JUCE_USE_ANDROID_OPENSLES
#if ! JUCE_USE_ANDROID_OBOE && JUCE_ANDROID_API_VERSION >= 9
#define JUCE_USE_ANDROID_OPENSLES 1
#else
#define JUCE_USE_ANDROID_OPENSLES 0
#endif
#endif
/** Config: JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
Turning this on gives your app exclusive access to the system's audio
on platforms which support it (currently iOS only).
*/
#ifndef JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
#define JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 0
#endif
//==============================================================================
#include "midi_io/juce_MidiDevices.h"
#include "midi_io/juce_MidiMessageCollector.h"
#include "audio_io/juce_AudioIODevice.h"
#include "audio_io/juce_AudioIODeviceType.h"
#include "audio_io/juce_SystemAudioVolume.h"
#include "sources/juce_AudioSourcePlayer.h"
#include "sources/juce_AudioTransportSource.h"
#include "audio_io/juce_AudioDeviceManager.h"
#if JUCE_IOS
#include "native/juce_ios_Audio.h"
#endif

+ 23
- 0
libs/juce-current/source/juce_audio_devices/juce_audio_devices.mm View File

@@ -0,0 +1,23 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "juce_audio_devices.cpp"

+ 153
- 0
libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiDevices.cpp View File

@@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier)
: Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier)
{
}
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer)
{
for (const auto metadata : buffer)
sendMessageNow (metadata.getMessage());
}
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer,
double millisecondCounterToStartAt,
double samplesPerSecondForBuffer)
{
// You've got to call startBackgroundThread() for this to actually work..
jassert (isThreadRunning());
// this needs to be a value in the future - RTFM for this method!
jassert (millisecondCounterToStartAt > 0);
auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
for (const auto metadata : buffer)
{
auto eventTime = millisecondCounterToStartAt + timeScaleFactor * metadata.samplePosition;
auto* m = new PendingMessage (metadata.data, metadata.numBytes, eventTime);
const ScopedLock sl (lock);
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime)
{
m->next = firstMessage;
firstMessage = m;
}
else
{
auto* mm = firstMessage;
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime)
mm = mm->next;
m->next = mm->next;
mm->next = m;
}
}
notify();
}
void MidiOutput::clearAllPendingMessages()
{
const ScopedLock sl (lock);
while (firstMessage != nullptr)
{
auto* m = firstMessage;
firstMessage = firstMessage->next;
delete m;
}
}
void MidiOutput::startBackgroundThread()
{
startThread (9);
}
void MidiOutput::stopBackgroundThread()
{
stopThread (5000);
}
void MidiOutput::run()
{
while (! threadShouldExit())
{
auto now = Time::getMillisecondCounter();
uint32 eventTime = 0;
uint32 timeToWait = 500;
PendingMessage* message;
{
const ScopedLock sl (lock);
message = firstMessage;
if (message != nullptr)
{
eventTime = (uint32) roundToInt (message->message.getTimeStamp());
if (eventTime > now + 20)
{
timeToWait = eventTime - (now + 20);
message = nullptr;
}
else
{
firstMessage = message->next;
}
}
}
if (message != nullptr)
{
std::unique_ptr<PendingMessage> messageDeleter (message);
if (eventTime > now)
{
Time::waitForMillisecondCounter (eventTime);
if (threadShouldExit())
break;
}
if (eventTime > now - 200)
sendMessageNow (message->message);
}
else
{
jassert (timeToWait < 1000 * 30);
wait ((int) timeToWait);
}
}
clearAllPendingMessages();
}
} // namespace juce

+ 378
- 0
libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiDevices.h View File

@@ -0,0 +1,378 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This struct contains information about a MIDI input or output device.
You can get one of these structs by calling the static getAvailableDevices() or
getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo()
on an instance of these classes. Devices can be opened by passing the identifier to
the openDevice() method.
@tags{Audio}
*/
struct MidiDeviceInfo
{
MidiDeviceInfo() = default;
MidiDeviceInfo (const String& deviceName, const String& deviceIdentifier)
: name (deviceName), identifier (deviceIdentifier)
{
}
/** The name of this device.
This will be provided by the OS unless the device has been created with the
createNewDevice() method.
Note that the name is not guaranteed to be unique and two devices with the
same name will be indistinguishable. If you want to address a specific device
it is better to use the identifier.
*/
String name;
/** The identifier for this device.
This will be provided by the OS and it's format will differ on different systems
e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string.
*/
String identifier;
//==============================================================================
bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; }
bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); }
};
class MidiInputCallback;
//==============================================================================
/**
Represents a midi input device.
To create one of these, use the static getAvailableDevices() method to find out what
inputs are available, and then use the openDevice() method to try to open one.
@see MidiOutput
@tags{Audio}
*/
class JUCE_API MidiInput final
{
public:
//==============================================================================
/** Returns a list of the available midi input devices.
You can open one of the devices by passing its identifier into the openDevice() method.
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice
*/
static Array<MidiDeviceInfo> getAvailableDevices();
/** Returns the MidiDeviceInfo of the default midi input device to use. */
static MidiDeviceInfo getDefaultDevice();
/** Tries to open one of the midi input devices.
This will return a MidiInput object if it manages to open it, you can then
call start() and stop() on this device.
If the device can't be opened, this will return an empty object.
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to
find the available devices that can be opened
@param callback the object that will receive the midi messages from this device
@see MidiInputCallback, getDevices
*/
static std::unique_ptr<MidiInput> openDevice (const String& deviceIdentifier, MidiInputCallback* callback);
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN
/** This will try to create a new midi input device (only available on Linux, macOS and iOS).
This will attempt to create a new midi input device with the specified name for other
apps to connect to.
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability"
setting in the iOS exporter otherwise this method will fail.
Returns an empty object if a device can't be created.
@param deviceName the name of the device to create
@param callback the object that will receive the midi messages from this device
*/
static std::unique_ptr<MidiInput> createNewDevice (const String& deviceName, MidiInputCallback* callback);
#endif
//==============================================================================
/** Destructor. */
~MidiInput();
/** Starts the device running.
After calling this, the device will start sending midi messages to the MidiInputCallback
object that was specified when the openDevice() method was called.
@see stop
*/
void start();
/** Stops the device running.
@see start
*/
void stop();
/** Returns the MidiDeviceInfo struct containing some information about this device. */
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
/** Returns the identifier of this device. */
String getIdentifier() const noexcept { return deviceInfo.identifier; }
/** Returns the name of this device. */
String getName() const noexcept { return deviceInfo.name; }
/** Sets a custom name for the device. */
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
//==============================================================================
/** Deprecated. */
static StringArray getDevices();
/** Deprecated. */
static int getDefaultDeviceIndex();
/** Deprecated. */
static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*);
private:
//==============================================================================
explicit MidiInput (const String&, const String&);
MidiDeviceInfo deviceInfo;
void* internal = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput)
};
//==============================================================================
/**
Receives incoming messages from a physical MIDI input device.
This class is overridden to handle incoming midi messages. See the MidiInput
class for more details.
@see MidiInput
@tags{Audio}
*/
class JUCE_API MidiInputCallback
{
public:
/** Destructor. */
virtual ~MidiInputCallback() = default;
/** Receives an incoming message.
A MidiInput object will call this method when a midi event arrives. It'll be
called on a high-priority system thread, so avoid doing anything time-consuming
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful
for queueing incoming messages for use later.
@param source the MidiInput object that generated the message
@param message the incoming message. The message's timestamp is set to a value
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the
time when the message arrived
*/
virtual void handleIncomingMidiMessage (MidiInput* source,
const MidiMessage& message) = 0;
/** Notification sent each time a packet of a multi-packet sysex message arrives.
If a long sysex message is broken up into multiple packets, this callback is made
for each packet that arrives until the message is finished, at which point
the normal handleIncomingMidiMessage() callback will be made with the entire
message.
The message passed in will contain the start of a sysex, but won't be finished
with the terminating 0xf7 byte.
*/
virtual void handlePartialSysexMessage (MidiInput* source,
const uint8* messageData,
int numBytesSoFar,
double timestamp)
{
ignoreUnused (source, messageData, numBytesSoFar, timestamp);
}
};
//==============================================================================
/**
Represents a midi output device.
To create one of these, use the static getAvailableDevices() method to find out what
outputs are available, and then use the openDevice() method to try to open one.
@see MidiInput
@tags{Audio}
*/
class JUCE_API MidiOutput final : private Thread
{
public:
//==============================================================================
/** Returns a list of the available midi output devices.
You can open one of the devices by passing its identifier into the openDevice() method.
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice
*/
static Array<MidiDeviceInfo> getAvailableDevices();
/** Returns the MidiDeviceInfo of the default midi output device to use. */
static MidiDeviceInfo getDefaultDevice();
/** Tries to open one of the midi output devices.
This will return a MidiOutput object if it manages to open it, you can then
send messages to this device.
If the device can't be opened, this will return an empty object.
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to
find the available devices that can be opened
@see getDevices
*/
static std::unique_ptr<MidiOutput> openDevice (const String& deviceIdentifier);
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN
/** This will try to create a new midi output device (only available on Linux, macOS and iOS).
This will attempt to create a new midi output device with the specified name that other
apps can connect to and use as their midi input.
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability"
setting in the iOS exporter otherwise this method will fail.
Returns an empty object if a device can't be created.
@param deviceName the name of the device to create
*/
static std::unique_ptr<MidiOutput> createNewDevice (const String& deviceName);
#endif
//==============================================================================
/** Destructor. */
~MidiOutput() override;
/** Returns the MidiDeviceInfo struct containing some information about this device. */
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
/** Returns the identifier of this device. */
String getIdentifier() const noexcept { return deviceInfo.identifier; }
/** Returns the name of this device. */
String getName() const noexcept { return deviceInfo.name; }
/** Sets a custom name for the device. */
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
//==============================================================================
/** Sends out a MIDI message immediately. */
void sendMessageNow (const MidiMessage& message);
/** Sends out a sequence of MIDI messages immediately. */
void sendBlockOfMessagesNow (const MidiBuffer& buffer);
/** This lets you supply a block of messages that will be sent out at some point
in the future.
The MidiOutput class has an internal thread that can send out timestamped
messages - this appends a set of messages to its internal buffer, ready for
sending.
This will only work if you've already started the thread with startBackgroundThread().
A time is specified, at which the block of messages should be sent. This time uses
the same time base as Time::getMillisecondCounter(), and must be in the future.
The samplesPerSecondForBuffer parameter indicates the number of samples per second
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the
samplesPerSecondForBuffer value is needed to convert this sample position to a
real time.
*/
void sendBlockOfMessages (const MidiBuffer& buffer,
double millisecondCounterToStartAt,
double samplesPerSecondForBuffer);
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */
void clearAllPendingMessages();
/** Starts up a background thread so that the device can send blocks of data.
Call this to get the device ready, before using sendBlockOfMessages().
*/
void startBackgroundThread();
/** Stops the background thread, and clears any pending midi events.
@see startBackgroundThread
*/
void stopBackgroundThread();
/** Returns true if the background thread used to send blocks of data is running.
@see startBackgroundThread, stopBackgroundThread
*/
bool isBackgroundThreadRunning() const noexcept { return isThreadRunning(); }
//==============================================================================
/** Deprecated. */
static StringArray getDevices();
/** Deprecated. */
static int getDefaultDeviceIndex();
/** Deprecated. */
static std::unique_ptr<MidiOutput> openDevice (int);
private:
//==============================================================================
struct PendingMessage
{
PendingMessage (const void* data, int len, double timeStamp)
: message (data, len, timeStamp)
{
}
MidiMessage message;
PendingMessage* next;
};
//==============================================================================
explicit MidiOutput (const String&, const String&);
void run() override;
MidiDeviceInfo deviceInfo;
void* internal = nullptr;
CriticalSection lock;
PendingMessage* firstMessage = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput)
};
} // namespace juce

+ 158
- 0
libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp View File

@@ -0,0 +1,158 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MidiMessageCollector::MidiMessageCollector()
{
}
MidiMessageCollector::~MidiMessageCollector()
{
}
//==============================================================================
void MidiMessageCollector::reset (const double newSampleRate)
{
const ScopedLock sl (midiCallbackLock);
jassert (newSampleRate > 0);
#if JUCE_DEBUG
hasCalledReset = true;
#endif
sampleRate = newSampleRate;
incomingMessages.clear();
lastCallbackTime = Time::getMillisecondCounterHiRes();
}
void MidiMessageCollector::addMessageToQueue (const MidiMessage& message)
{
const ScopedLock sl (midiCallbackLock);
#if JUCE_DEBUG
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
#endif
// the messages that come in here need to be time-stamped correctly - see MidiInput
// for details of what the number should be.
jassert (message.getTimeStamp() != 0);
auto sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate);
incomingMessages.addEvent (message, sampleNumber);
// if the messages don't get used for over a second, we'd better
// get rid of any old ones to avoid the queue getting too big
if (sampleNumber > sampleRate)
incomingMessages.clear (0, sampleNumber - (int) sampleRate);
}
void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer,
const int numSamples)
{
const ScopedLock sl (midiCallbackLock);
#if JUCE_DEBUG
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
#endif
jassert (numSamples > 0);
auto timeNow = Time::getMillisecondCounterHiRes();
auto msElapsed = timeNow - lastCallbackTime;
lastCallbackTime = timeNow;
if (! incomingMessages.isEmpty())
{
int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate));
int startSample = 0;
int scale = 1 << 16;
if (numSourceSamples > numSamples)
{
// if our list of events is longer than the buffer we're being
// asked for, scale them down to squeeze them all in..
const int maxBlockLengthToUse = numSamples << 5;
auto iter = incomingMessages.cbegin();
if (numSourceSamples > maxBlockLengthToUse)
{
startSample = numSourceSamples - maxBlockLengthToUse;
numSourceSamples = maxBlockLengthToUse;
iter = incomingMessages.findNextSamplePosition (startSample);
}
scale = (numSamples << 10) / numSourceSamples;
std::for_each (iter, incomingMessages.cend(), [&] (const MidiMessageMetadata& meta)
{
const auto pos = ((meta.samplePosition - startSample) * scale) >> 10;
destBuffer.addEvent (meta.data, meta.numBytes, jlimit (0, numSamples - 1, pos));
});
}
else
{
// if our event list is shorter than the number we need, put them
// towards the end of the buffer
startSample = numSamples - numSourceSamples;
for (const auto metadata : incomingMessages)
destBuffer.addEvent (metadata.data, metadata.numBytes,
jlimit (0, numSamples - 1, metadata.samplePosition + startSample));
}
incomingMessages.clear();
}
}
void MidiMessageCollector::ensureStorageAllocated (size_t bytes)
{
incomingMessages.ensureSize (bytes);
}
//==============================================================================
void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
addMessageToQueue (m);
}
void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
addMessageToQueue (m);
}
void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
{
addMessageToQueue (message);
}
} // namespace juce

+ 113
- 0
libs/juce-current/source/juce_audio_devices/midi_io/juce_MidiMessageCollector.h View File

@@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Collects incoming realtime MIDI messages and turns them into blocks suitable for
processing by a block-based audio callback.
The class can also be used as either a MidiKeyboardState::Listener or a MidiInputCallback
so it can easily use a midi input or keyboard component as its source.
@see MidiMessage, MidiInput
@tags{Audio}
*/
class JUCE_API MidiMessageCollector : public MidiKeyboardState::Listener,
public MidiInputCallback
{
public:
//==============================================================================
/** Creates a MidiMessageCollector. */
MidiMessageCollector();
/** Destructor. */
~MidiMessageCollector() override;
//==============================================================================
/** Clears any messages from the queue.
You need to call this method before starting to use the collector, so that
it knows the correct sample rate to use.
*/
void reset (double sampleRate);
/** Takes an incoming real-time message and adds it to the queue.
The message's timestamp is taken, and it will be ready for retrieval as part
of the block returned by the next call to removeNextBlockOfMessages().
This method is fully thread-safe when overlapping calls are made with
removeNextBlockOfMessages().
*/
void addMessageToQueue (const MidiMessage& message);
/** Removes all the pending messages from the queue as a buffer.
This will also correct the messages' timestamps to make sure they're in
the range 0 to numSamples - 1.
This call should be made regularly by something like an audio processing
callback, because the time that it happens is used in calculating the
midi event positions.
This method is fully thread-safe when overlapping calls are made with
addMessageToQueue().
Precondition: numSamples must be greater than 0.
*/
void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples);
/** Preallocates storage for collected messages.
This can be called before audio processing begins to ensure that there
is sufficient space for the expected MIDI messages, in order to avoid
allocations within the audio callback.
*/
void ensureStorageAllocated (size_t bytes);
//==============================================================================
/** @internal */
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
/** @internal */
void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
/** @internal */
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
private:
//==============================================================================
double lastCallbackTime = 0;
CriticalSection midiCallbackLock;
MidiBuffer incomingMessages;
double sampleRate = 44100.0;
#if JUCE_DEBUG
bool hasCalledReset = false;
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector)
};
} // namespace juce

+ 1094
- 0
libs/juce-current/source/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java
File diff suppressed because it is too large
View File


+ 188
- 0
libs/juce-current/source/juce_audio_devices/native/juce_MidiDataConcatenator.h View File

@@ -0,0 +1,188 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Helper class that takes chunks of incoming midi bytes, packages them into
messages, and dispatches them to a midi callback.
@tags{Audio}
*/
class MidiDataConcatenator
{
public:
MidiDataConcatenator (int initialBufferSize)
: pendingSysexData ((size_t) initialBufferSize)
{
}
void reset()
{
currentMessageLen = 0;
pendingSysexSize = 0;
pendingSysexTime = 0;
}
template <typename UserDataType, typename CallbackType>
void pushMidiData (const void* inputData, int numBytes, double time,
UserDataType* input, CallbackType& callback)
{
auto d = static_cast<const uint8*> (inputData);
while (numBytes > 0)
{
auto nextByte = *d;
if (pendingSysexSize != 0 || nextByte == 0xf0)
{
processSysex (d, numBytes, time, input, callback);
currentMessageLen = 0;
continue;
}
++d;
--numBytes;
if (isRealtimeMessage (nextByte))
{
callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time));
// These can be embedded in the middle of a normal message, so we won't
// reset the currentMessageLen here.
continue;
}
if (isInitialByte (nextByte))
{
currentMessage[0] = nextByte;
currentMessageLen = 1;
}
else if (currentMessageLen > 0 && currentMessageLen < 3)
{
currentMessage[currentMessageLen++] = nextByte;
}
else
{
// message is too long or invalid MIDI - abandon it and start again with the next byte
currentMessageLen = 0;
continue;
}
auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]);
if (expectedLength == currentMessageLen)
{
callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time));
currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte
}
}
}
private:
template <typename UserDataType, typename CallbackType>
void processSysex (const uint8*& d, int& numBytes, double time,
UserDataType* input, CallbackType& callback)
{
if (*d == 0xf0)
{
pendingSysexSize = 0;
pendingSysexTime = time;
}
pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false);
auto totalMessage = static_cast<uint8*> (pendingSysexData.getData());
auto dest = totalMessage + pendingSysexSize;
do
{
if (pendingSysexSize > 0 && isStatusByte (*d))
{
if (*d == 0xf7)
{
*dest++ = *d++;
++pendingSysexSize;
--numBytes;
break;
}
if (*d >= 0xfa || *d == 0xf8)
{
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time));
++d;
--numBytes;
}
else
{
pendingSysexSize = 0;
int used = 0;
const MidiMessage m (d, numBytes, used, 0, time);
if (used > 0)
{
callback.handleIncomingMidiMessage (input, m);
numBytes -= used;
d += used;
}
break;
}
}
else
{
*dest++ = *d++;
++pendingSysexSize;
--numBytes;
}
}
while (numBytes > 0);
if (pendingSysexSize > 0)
{
if (totalMessage [pendingSysexSize - 1] == 0xf7)
{
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime));
pendingSysexSize = 0;
}
else
{
callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime);
}
}
}
static bool isRealtimeMessage (uint8 byte) { return byte >= 0xf8 && byte <= 0xfe; }
static bool isStatusByte (uint8 byte) { return byte >= 0x80; }
static bool isInitialByte (uint8 byte) { return isStatusByte (byte) && byte != 0xf7; }
uint8 currentMessage[3];
int currentMessageLen = 0;
MemoryBlock pendingSysexData;
double pendingSysexTime = 0;
int pendingSysexSize = 0;
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator)
};
} // namespace juce

+ 496
- 0
libs/juce-current/source/juce_audio_devices/native/juce_android_Audio.cpp View File

@@ -0,0 +1,496 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \
METHOD (constructor, "<init>", "(IIIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (play, "play", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (release, "release", "()V") \
METHOD (flush, "flush", "()V") \
METHOD (write, "write", "([SII)I") \
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack")
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
METHOD (constructor, "<init>", "(IIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (startRecording, "startRecording", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (read, "read", "([SII)I") \
METHOD (release, "release", "()V") \
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord")
#undef JNI_CLASS_MEMBERS
//==============================================================================
enum
{
CHANNEL_OUT_STEREO = 12,
CHANNEL_IN_STEREO = 12,
CHANNEL_IN_MONO = 16,
ENCODING_PCM_16BIT = 2,
STREAM_MUSIC = 3,
MODE_STREAM = 1,
STATE_UNINITIALIZED = 0
};
const char* const javaAudioTypeName = "Android Audio";
//==============================================================================
class AndroidAudioIODevice : public AudioIODevice,
public Thread
{
public:
//==============================================================================
AndroidAudioIODevice (const String& deviceName)
: AudioIODevice (deviceName, javaAudioTypeName),
Thread ("audio"),
minBufferSizeOut (0), minBufferSizeIn (0), callback (nullptr), sampleRate (0),
numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2),
numClientOutputChannels (0), numDeviceOutputChannels (0),
actualBufferSize (0), isRunning (false),
inputChannelBuffer (1, 1),
outputChannelBuffer (1, 1)
{
JNIEnv* env = getEnv();
sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM);
minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);
minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT);
if (minBufferSizeIn <= 0)
{
minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT);
if (minBufferSizeIn > 0)
numDeviceInputChannelsAvailable = 1;
else
numDeviceInputChannelsAvailable = 0;
}
DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; "
<< sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable);
}
~AndroidAudioIODevice() override
{
close();
}
StringArray getOutputChannelNames() override
{
StringArray s;
s.add ("Left");
s.add ("Right");
return s;
}
StringArray getInputChannelNames() override
{
StringArray s;
if (numDeviceInputChannelsAvailable == 2)
{
s.add ("Left");
s.add ("Right");
}
else if (numDeviceInputChannelsAvailable == 1)
{
s.add ("Audio Input");
}
return s;
}
Array<double> getAvailableSampleRates() override
{
Array<double> r;
r.add ((double) sampleRate);
return r;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> b;
int n = 16;
for (int i = 0; i < 50; ++i)
{
b.add (n);
n += n < 64 ? 16
: (n < 512 ? 32
: (n < 1024 ? 64
: (n < 2048 ? 128 : 256)));
}
return b;
}
int getDefaultBufferSize() override { return 2048; }
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double requestedSampleRate,
int bufferSize) override
{
close();
if (sampleRate != (int) requestedSampleRate)
return "Sample rate not allowed";
lastError.clear();
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
numDeviceInputChannels = 0;
numDeviceOutputChannels = 0;
activeOutputChans = outputChannels;
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
numClientOutputChannels = activeOutputChans.countNumberOfSetBits();
activeInputChans = inputChannels;
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
numClientInputChannels = activeInputChans.countNumberOfSetBits();
actualBufferSize = preferredBufferSize;
inputChannelBuffer.setSize (2, actualBufferSize);
inputChannelBuffer.clear();
outputChannelBuffer.setSize (2, actualBufferSize);
outputChannelBuffer.clear();
JNIEnv* env = getEnv();
if (numClientOutputChannels > 0)
{
numDeviceOutputChannels = 2;
outputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioTrack, AudioTrack.constructor,
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT,
(jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM)));
const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24);
getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr;
int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
if (outputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
outputDevice.clear();
lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState);
}
}
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0)
{
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
{
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
// before trying to open an audio input device. This is not going to work!
jassertfalse;
inputDevice.clear();
lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO";
}
else
{
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
inputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioRecord, AudioRecord.constructor,
0 /* (default audio source) */, sampleRate,
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
ENCODING_PCM_16BIT,
(jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16))))));
int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState);
if (inputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
inputDevice.clear();
lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState);
}
}
}
if (isRunning)
{
if (outputDevice != nullptr)
env->CallVoidMethod (outputDevice, AudioTrack.play);
if (inputDevice != nullptr)
env->CallVoidMethod (inputDevice, AudioRecord.startRecording);
startThread (8);
}
else
{
closeDevices();
}
return lastError;
}
void close() override
{
if (isRunning)
{
stopThread (2000);
isRunning = false;
closeDevices();
}
}
int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; }
int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; }
bool isOpen() override { return isRunning; }
int getCurrentBufferSizeSamples() override { return actualBufferSize; }
int getCurrentBitDepth() override { return 16; }
double getCurrentSampleRate() override { return sampleRate; }
BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
BigInteger getActiveInputChannels() const override { return activeInputChans; }
String getLastError() override { return lastError; }
bool isPlaying() override { return isRunning && callback != nullptr; }
int getXRunCount() const noexcept override
{
if (outputDevice != nullptr && getUnderrunCount != nullptr)
return getEnv()->CallIntMethod (outputDevice, getUnderrunCount);
return -1;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (isRunning && callback != newCallback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
const ScopedLock sl (callbackLock);
callback = newCallback;
}
}
void stop() override
{
if (isRunning)
{
AudioIODeviceCallback* lastCallback;
{
const ScopedLock sl (callbackLock);
lastCallback = callback;
callback = nullptr;
}
if (lastCallback != nullptr)
lastCallback->audioDeviceStopped();
}
}
void run() override
{
JNIEnv* env = getEnv();
jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels));
while (! threadShouldExit())
{
if (inputDevice != nullptr)
{
jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels);
if (numRead < actualBufferSize * numDeviceInputChannels)
{
DBG ("Audio read under-run! " << numRead);
}
jshort* const src = env->GetShortArrayElements (audioBuffer, nullptr);
for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan)
{
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan));
if (chan < numDeviceInputChannels)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels);
d.convertSamples (s, actualBufferSize);
}
else
{
d.clearSamples (actualBufferSize);
}
}
env->ReleaseShortArrayElements (audioBuffer, src, 0);
}
if (threadShouldExit())
break;
{
const ScopedLock sl (callbackLock);
if (callback != nullptr)
{
callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels,
outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels,
actualBufferSize);
}
else
{
outputChannelBuffer.clear();
}
}
if (outputDevice != nullptr)
{
if (threadShouldExit())
break;
jshort* const dest = env->GetShortArrayElements (audioBuffer, nullptr);
for (int chan = 0; chan < numDeviceOutputChannels; ++chan)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels);
const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1));
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData);
d.convertSamples (s, actualBufferSize);
}
env->ReleaseShortArrayElements (audioBuffer, dest, 0);
jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels);
if (numWritten < actualBufferSize * numDeviceOutputChannels)
{
DBG ("Audio write underrun! " << numWritten);
}
}
}
}
int minBufferSizeOut, minBufferSizeIn;
private:
//==============================================================================
CriticalSection callbackLock;
AudioIODeviceCallback* callback;
jint sampleRate;
int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable;
int numClientOutputChannels, numDeviceOutputChannels;
int actualBufferSize;
bool isRunning;
String lastError;
BigInteger activeOutputChans, activeInputChans;
GlobalRef outputDevice, inputDevice;
AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
jmethodID getUnderrunCount = nullptr;
void closeDevices()
{
if (outputDevice != nullptr)
{
outputDevice.callVoidMethod (AudioTrack.stop);
outputDevice.callVoidMethod (AudioTrack.release);
outputDevice.clear();
}
if (inputDevice != nullptr)
{
inputDevice.callVoidMethod (AudioRecord.stop);
inputDevice.callVoidMethod (AudioRecord.release);
inputDevice.clear();
}
}
JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice)
};
//==============================================================================
class AndroidAudioIODeviceType : public AudioIODeviceType
{
public:
AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {}
//==============================================================================
void scanForDevices() {}
StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); }
int getDefaultDeviceIndex (bool) const { return 0; }
int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; }
bool hasSeparateInputsAndOutputs() const { return false; }
AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName)
{
std::unique_ptr<AndroidAudioIODevice> dev;
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
{
dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
: inputDeviceName));
if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0)
dev = nullptr;
}
return dev.release();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType)
};
//==============================================================================
extern bool isOboeAvailable();
extern bool isOpenSLAvailable();
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
#if JUCE_USE_ANDROID_OBOE
if (isOboeAvailable())
return nullptr;
#endif
#if JUCE_USE_ANDROID_OPENSLES
if (isOpenSLAvailable())
return nullptr;
#endif
return new AndroidAudioIODeviceType();
}
} // namespace juce

+ 131
- 0
libs/juce-current/source/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h View File

@@ -0,0 +1,131 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Some shared helpers methods for using the high-performance audio paths on
Android devices (OpenSL and Oboe).
@tags{Audio}
*/
namespace AndroidHighPerformanceAudioHelpers
{
//==============================================================================
static double getNativeSampleRate()
{
return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
}
static int getNativeBufferSizeHint()
{
// This property is a hint of a native buffer size but it does not guarantee the size used.
auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue();
if (deviceBufferSize == 0)
return 192;
return deviceBufferSize;
}
static bool isProAudioDevice()
{
static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG")
&& DynamicLibrary().open ("libapa_jni.so");
return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported;
}
static bool hasLowLatencyAudioPath()
{
return androidHasSystemFeature ("android.hardware.audio.low_latency");
}
static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate)
{
return ((requestedBufferSize % nativeBufferSize) == 0)
&& (requestedSampleRate == getNativeSampleRate())
&& isProAudioDevice();
}
//==============================================================================
static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate)
{
if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate))
{
// see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp
// "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required
// for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one
// is sufficient for lower latency."
return (getAndroidSDKVersion() >= 18 ? 1 : 2);
}
// not using low-latency path so we can use the absolute minimum number of buffers to queue
return 1;
}
static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept
{
auto maxBufferFrames = static_cast<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0));
auto maxNumBuffers = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames)
/ static_cast<double> (nativeBufferSize)));
return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers);
}
static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept
{
static constexpr int maxBufferSizeMs = 200;
return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate));
}
static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> availableSampleRates)
{
auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate());
auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(),
availableSampleRates.size()));
Array<int> bufferSizes;
for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i)
bufferSizes.add (i * nativeBufferSize);
return bufferSizes;
}
static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate)
{
static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40;
static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100;
auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs
: defaultBufferSizeForStandardLatencyDeviceMs);
auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate);
return defaultBuffersToEnqueue * nativeBufferSize;
}
}
} // namespace juce

+ 706
- 0
libs/juce-current/source/juce_audio_devices/native/juce_android_Midi.cpp View File

@@ -0,0 +1,706 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
// This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23
// See juce_core/native/java/README.txt on how to generate this byte-code.
static const uint8 javaMidiByteCode[] =
{31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220,
69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45,
201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81,
80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204,
156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248,
199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3,
250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200,
6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66,
224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205,
68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101,
192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188,
3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151,
1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191,
1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121,
224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239,
129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53,
224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224,
23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86,
224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4,
136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68,
21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60,
96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24,
184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64,
12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224,
108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216,
11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33,
252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132,
37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128,
29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127,
252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4,
210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216,
88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251,
208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62,
230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151,
159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190,
88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29,
109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163,
40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253,
51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110,
83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164,
197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167,
22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224,
216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117,
192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116,
189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199,
213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164,
159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105,
59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235,
217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221,
64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252,
94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124,
181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91,
243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89,
180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78,
199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79,
168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94,
247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232,
109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116,
187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158,
21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179,
82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159,
77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204,
127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209,
252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207,
117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7,
239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108,
203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251,
13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67,
92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168,
241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183,
49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55,
168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223,
13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247,
253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98,
237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252,
74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101,
230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78,
172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55,
170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81,
197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219,
5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95,
25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56,
227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203,
90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192,
127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114,
105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254,
27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220,
174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71,
137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66,
75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116,
221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105,
7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189,
10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158,
205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5,
196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253,
230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125,
196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68,
154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179,
252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30,
44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74,
79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90,
68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7,
41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161,
247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250,
105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8,
255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93,
66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13,
218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52,
151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9,
255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145,
154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4,
229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157,
220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200,
197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11,
164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218,
106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235,
122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21,
73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219,
210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66,
146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80,
43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211,
249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113,
244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240,
166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160,
208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39,
245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38,
158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253,
158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192,
208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246,
177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235,
81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111,
122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101,
44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3,
34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7,
114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176,
144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207,
231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236,
183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120,
152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96,
94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130,
243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129,
117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245,
172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122,
162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174,
240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245,
61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117,
234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179,
86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160,
171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238,
63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223,
90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238,
25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34,
104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196,
164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186,
169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118,
187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101,
11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47,
206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110,
157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91,
150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245,
125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62,
109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46,
153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185,
81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24,
83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121,
38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217,
95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134,
137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150,
129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73,
144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173,
205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43,
223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215,
241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186,
90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101,
19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153,
30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250,
215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27,
163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130,
249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34,
107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153,
167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127,
103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196,
38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138,
79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199,
199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163,
121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208,
151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163,
163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110,
231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221,
22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251,
233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70,
205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191,
173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251,
226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216,
165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116,
28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194,
76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181,
188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119,
77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190,
192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173,
88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168,
214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29,
200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176,
116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106,
254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219,
157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202,
19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105,
167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77,
146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76,
185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228,
29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198,
167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26,
74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80,
26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229,
198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249,
51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121,
246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199,
70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70,
102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200,
188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193,
216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201,
93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242,
252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175,
243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109,
27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140,
146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176,
68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213,
58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38,
226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184,
186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217,
34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38,
144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115,
200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38,
101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112,
214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28,
217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42,
76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223,
42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126,
208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227,
213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164,
147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205,
194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213,
163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83,
199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250,
77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74,
201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154,
244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127,
11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146,
29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35,
143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33,
46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163,
29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160,
245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102,
188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240,
91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253,
30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105,
137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122,
186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244,
155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137,
5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233,
249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228,
39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49,
177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227,
13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179,
189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94,
238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196,
0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23,
107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161,
223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57,
174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108,
143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240,
229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232,
105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198,
190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56,
124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72,
172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25,
117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118,
88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198,
127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184,
32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148,
46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179,
39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85,
105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48,
220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123,
137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182,
235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64,
244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110,
26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181,
74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15,
102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195,
186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132,
107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157,
74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12,
255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248,
248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0};
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \
STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;")
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode))
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \
METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \
METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \
METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (start, "start", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (close, "close", "()V") \
METHOD (sendMidi, "sendMidi", "([BII)V") \
METHOD (getName, "getName", "()Ljava/lang/String;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23)
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidMidiInput
{
public:
AndroidMidiInput (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
: juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048),
javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID,
(jint) deviceID, (jlong) this)))
{
}
~AndroidMidiInput()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
bool isOpen() const noexcept
{
return javaMidiDevice != nullptr;
}
void start()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.start);
}
void stop()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
callback = nullptr;
}
String getName() const noexcept
{
if (jobject d = javaMidiDevice.get())
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName)));
return {};
}
void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
{
auto* env = getEnv();
jassert (byteArray != nullptr);
auto* data = env->GetByteArrayElements (byteArray, nullptr);
HeapBlock<uint8> buffer (static_cast<size_t> (len));
std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));
midiConcatenator.pushMidiData (buffer.get(),
len, static_cast<double> (timestamp) * 1.0e-9,
juceMidiInput, *callback);
env->ReleaseByteArrayElements (byteArray, data, 0);
}
static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray,
jint offset, jint len, jlong timestamp)
{
auto* myself = reinterpret_cast<AndroidMidiInput*> (host);
myself->handleMidi (byteArray, offset, len, timestamp);
}
private:
MidiInput* juceMidiInput;
MidiInputCallback* callback;
MidiDataConcatenator midiConcatenator;
GlobalRef javaMidiDevice;
};
//==============================================================================
class AndroidMidiOutput
{
public:
AndroidMidiOutput (const LocalRef<jobject>& midiDevice)
: javaMidiDevice (midiDevice)
{
}
~AndroidMidiOutput()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
void send (jbyteArray byteArray, jint offset, jint len)
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d,
JuceMidiPort.sendMidi,
byteArray, offset, len);
}
String getName() const noexcept
{
if (jobject d = javaMidiDevice.get())
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName)));
return {};
}
private:
GlobalRef javaMidiDevice;
};
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" )
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23)
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidMidiDeviceManager
{
public:
AndroidMidiDeviceManager()
: deviceManager (LocalRef<jobject>(getEnv()->CallStaticObjectMethod (JuceMidiSupport,
JuceMidiSupport.getAndroidMidiDeviceManager,
getAppContext().get())))
{
}
Array<MidiDeviceInfo> getDevices (bool input)
{
if (jobject dm = deviceManager.get())
{
jobjectArray jDeviceNameAndIDs
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs
: MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs);
// Create a local reference as converting this to a JUCE string will call into JNI
LocalRef<jobjectArray> localDeviceNameAndIDs (jDeviceNameAndIDs);
auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs);
deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
Array<MidiDeviceInfo> devices;
for (int i = 0; i < deviceNameAndIDs.size(); i += 2)
devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] });
return devices;
}
return {};
}
AndroidMidiInput* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
{
if (auto dm = deviceManager.get())
{
std::unique_ptr<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, deviceID, callback, dm));
if (androidMidiInput->isOpen())
return androidMidiInput.release();
}
return nullptr;
}
AndroidMidiOutput* openMidiOutputPortWithID (int deviceID)
{
if (auto dm = deviceManager.get())
if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID))
return new AndroidMidiOutput (LocalRef<jobject>(javaMidiPort));
return nullptr;
}
private:
GlobalRef deviceManager;
};
//==============================================================================
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
AndroidMidiDeviceManager manager;
return manager.getDevices (true);
}
MidiDeviceInfo MidiInput::getDefaultDevice()
{
if (getAndroidSDKVersion() < 23)
return {};
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty())
return {};
AndroidMidiDeviceManager manager;
std::unique_ptr<MidiInput> midiInput (new MidiInput ({}, deviceIdentifier));
if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback))
{
midiInput->internal = port;
midiInput->setName (port->getName());
return midiInput;
}
return {};
}
StringArray MidiInput::getDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiInput::getDefaultDeviceIndex()
{
return (getAndroidSDKVersion() < 23 ? -1 : 0);
}
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
: deviceInfo (deviceName, deviceIdentifier)
{
}
MidiInput::~MidiInput()
{
delete reinterpret_cast<AndroidMidiInput*> (internal);
}
void MidiInput::start()
{
if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal))
mi->start();
}
void MidiInput::stop()
{
if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal))
mi->stop();
}
//==============================================================================
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
AndroidMidiDeviceManager manager;
return manager.getDevices (false);
}
MidiDeviceInfo MidiOutput::getDefaultDevice()
{
if (getAndroidSDKVersion() < 23)
return {};
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
{
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty())
return {};
AndroidMidiDeviceManager manager;
if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue()))
{
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier));
midiOutput->internal = port;
midiOutput->setName (port->getName());
return midiOutput;
}
return {};
}
StringArray MidiOutput::getDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiOutput::getDefaultDeviceIndex()
{
return (getAndroidSDKVersion() < 23 ? -1 : 0);
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
{
return openDevice (getAvailableDevices()[index].identifier);
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
delete reinterpret_cast<AndroidMidiOutput*> (internal);
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
if (auto* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
{
auto* env = getEnv();
auto messageSize = message.getRawDataSize();
LocalRef<jbyteArray> messageContent (env->NewByteArray (messageSize));
auto content = messageContent.get();
auto* rawBytes = env->GetByteArrayElements (content, nullptr);
std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize));
env->ReleaseByteArrayElements (content, rawBytes, 0);
androidMidi->send (content, (jint) 0, (jint) messageSize);
}
}
} // namespace juce

+ 1442
- 0
libs/juce-current/source/juce_audio_devices/native/juce_android_Oboe.cpp
File diff suppressed because it is too large
View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save