| @@ -18,7 +18,7 @@ | |||
| #include "CarlaHost.h" | |||
| #include "CarlaString.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| namespace CB = CarlaBackend; | |||
| @@ -27,10 +27,10 @@ | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaBase64Utils.hpp" | |||
| #include "juce_audio_formats.h" | |||
| #include "juce_audio_formats/juce_audio_formats.h" | |||
| #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
| # include "juce_gui_basics.h" | |||
| # include "juce_gui_basics/juce_gui_basics.h" | |||
| #else | |||
| namespace juce { | |||
| # include "juce_events/messages/juce_Initialisation.h" | |||
| @@ -28,7 +28,7 @@ | |||
| #include "CarlaHost.h" | |||
| #include "CarlaOscUtils.hpp" | |||
| #include "CarlaString.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| namespace CB = CarlaBackend; | |||
| @@ -25,10 +25,10 @@ | |||
| #include "CarlaThread.hpp" | |||
| #include "LinkedList.hpp" | |||
| #include "juce_audio_formats.h" | |||
| #include "juce_audio_formats/juce_audio_formats.h" | |||
| #ifdef CARLA_OS_MAC | |||
| # include "juce_audio_processors.h" | |||
| # include "juce_audio_processors/juce_audio_processors.h" | |||
| #endif | |||
| #include "../native-plugins/_data.cpp" | |||
| @@ -35,7 +35,7 @@ | |||
| #include "CarlaMIDI.h" | |||
| #include "jackbridge/JackBridge.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| using juce::CharPointer_UTF8; | |||
| using juce::File; | |||
| @@ -22,6 +22,15 @@ | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "CarlaMIDI.h" | |||
| // FIXME: update to new Juce API | |||
| #if defined(__clang__) | |||
| # pragma clang diagnostic push | |||
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||
| #endif | |||
| using juce::AudioPluginInstance; | |||
| using juce::AudioProcessor; | |||
| using juce::AudioProcessorEditor; | |||
| @@ -1045,12 +1054,12 @@ const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t | |||
| } | |||
| else if (portId >= kAudioOutputPortOffset) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(proc->getNumOutputChannels() > 0, String()); | |||
| CARLA_SAFE_ASSERT_RETURN(proc->getTotalNumOutputChannels() > 0, String()); | |||
| fullPortName += ":" + proc->getOutputChannelName(static_cast<int>(portId-kAudioOutputPortOffset)); | |||
| } | |||
| else if (portId >= kAudioInputPortOffset) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(proc->getNumInputChannels() > 0, String()); | |||
| CARLA_SAFE_ASSERT_RETURN(proc->getTotalNumInputChannels() > 0, String()); | |||
| fullPortName += ":" + proc->getInputChannelName(static_cast<int>(portId-kAudioInputPortOffset)); | |||
| } | |||
| else | |||
| @@ -1070,13 +1079,13 @@ void addNodeToPatchbay(CarlaEngine* const engine, const uint32_t groupId, const | |||
| const int icon((clientId >= 0) ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE); | |||
| engine->callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, groupId, icon, clientId, 0.0f, proc->getName().toRawUTF8()); | |||
| for (int i=0, numInputs=proc->getNumInputChannels(); i<numInputs; ++i) | |||
| for (int i=0, numInputs=proc->getTotalNumInputChannels(); i<numInputs; ++i) | |||
| { | |||
| engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast<int>(kAudioInputPortOffset)+i, | |||
| PATCHBAY_PORT_TYPE_AUDIO|PATCHBAY_PORT_IS_INPUT, 0.0f, proc->getInputChannelName(i).toRawUTF8()); | |||
| } | |||
| for (int i=0, numOutputs=proc->getNumOutputChannels(); i<numOutputs; ++i) | |||
| for (int i=0, numOutputs=proc->getTotalNumOutputChannels(); i<numOutputs; ++i) | |||
| { | |||
| engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast<int>(kAudioOutputPortOffset)+i, | |||
| PATCHBAY_PORT_TYPE_AUDIO, 0.0f, proc->getOutputChannelName(i).toRawUTF8()); | |||
| @@ -1101,13 +1110,13 @@ void removeNodeFromPatchbay(CarlaEngine* const engine, const uint32_t groupId, c | |||
| CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); | |||
| for (int i=0, numInputs=proc->getNumInputChannels(); i<numInputs; ++i) | |||
| for (int i=0, numInputs=proc->getTotalNumInputChannels(); i<numInputs; ++i) | |||
| { | |||
| engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast<int>(kAudioInputPortOffset)+i, | |||
| 0, 0.0f, nullptr); | |||
| } | |||
| for (int i=0, numOutputs=proc->getNumOutputChannels(); i<numOutputs; ++i) | |||
| for (int i=0, numOutputs=proc->getTotalNumOutputChannels(); i<numOutputs; ++i) | |||
| { | |||
| engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast<int>(kAudioOutputPortOffset)+i, | |||
| 0, 0.0f, nullptr); | |||
| @@ -1799,7 +1808,7 @@ bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const cha | |||
| return true; | |||
| } | |||
| for (int j=0, numInputs=proc->getNumInputChannels(); j<numInputs; ++j) | |||
| for (int j=0, numInputs=proc->getTotalNumInputChannels(); j<numInputs; ++j) | |||
| { | |||
| if (proc->getInputChannelName(j) != portName) | |||
| continue; | |||
| @@ -1808,7 +1817,7 @@ bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const cha | |||
| return true; | |||
| } | |||
| for (int j=0, numOutputs=proc->getNumOutputChannels(); j<numOutputs; ++j) | |||
| for (int j=0, numOutputs=proc->getTotalNumOutputChannels(); j<numOutputs; ++j) | |||
| { | |||
| if (proc->getOutputChannelName(j) != portName) | |||
| continue; | |||
| @@ -2252,4 +2261,11 @@ bool CarlaEngine::disconnectExternalGraphPort(const uint connectionType, const u | |||
| CARLA_BACKEND_END_NAMESPACE | |||
| // enable -Wdeprecated-declarations again | |||
| #if defined(__clang__) | |||
| # pragma clang diagnostic pop | |||
| #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| @@ -23,7 +23,8 @@ | |||
| #include "CarlaPatchbayUtils.hpp" | |||
| #include "CarlaStringList.hpp" | |||
| #include "juce_audio_processors.h" | |||
| #include "juce_audio_processors/juce_audio_processors.h" | |||
| using juce::AudioProcessorGraph; | |||
| using juce::AudioSampleBuffer; | |||
| using juce::MidiBuffer; | |||
| @@ -26,7 +26,7 @@ | |||
| #include "CarlaStringList.hpp" | |||
| #include "jackey.h" | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_basics/juce_audio_basics.h" | |||
| #ifdef __SSE2_MATH__ | |||
| # include <xmmintrin.h> | |||
| @@ -34,10 +34,10 @@ | |||
| #include "CarlaHost.h" | |||
| #include "CarlaNative.hpp" | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_basics/juce_audio_basics.h" | |||
| #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
| # include "juce_gui_basics.h" | |||
| # include "juce_gui_basics/juce_gui_basics.h" | |||
| #else | |||
| namespace juce { | |||
| # include "juce_events/messages/juce_Initialisation.h" | |||
| @@ -24,7 +24,7 @@ | |||
| #include "RtLinkedList.hpp" | |||
| #include "jackbridge/JackBridge.hpp" | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_basics/juce_audio_basics.h" | |||
| #include "rtaudio/RtAudio.h" | |||
| #include "rtmidi/RtMidi.h" | |||
| @@ -25,7 +25,7 @@ | |||
| #include <ctime> | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| using juce::CharPointer_UTF8; | |||
| using juce::File; | |||
| @@ -22,7 +22,7 @@ | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #include <fluidsynth.h> | |||
| @@ -28,7 +28,7 @@ | |||
| #include "CarlaString.hpp" | |||
| #include "RtLinkedList.hpp" | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_basics/juce_audio_basics.h" | |||
| using juce::FloatVectorOperations; | |||
| @@ -24,7 +24,7 @@ | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "JucePluginWindow.hpp" | |||
| #include "juce_audio_processors.h" | |||
| #include "juce_audio_processors/juce_audio_processors.h" | |||
| using namespace juce; | |||
| @@ -359,9 +359,9 @@ public: | |||
| bool needsCtrlIn, needsCtrlOut; | |||
| needsCtrlIn = needsCtrlOut = false; | |||
| aIns = (fInstance->getNumInputChannels() > 0) ? static_cast<uint32_t>(fInstance->getNumInputChannels()) : 0; | |||
| aOuts = (fInstance->getNumOutputChannels() > 0) ? static_cast<uint32_t>(fInstance->getNumOutputChannels()) : 0; | |||
| params = (fInstance->getNumParameters() > 0) ? static_cast<uint32_t>(fInstance->getNumParameters()) : 0; | |||
| aIns = (fInstance->getTotalNumInputChannels() > 0) ? static_cast<uint32_t>(fInstance->getTotalNumInputChannels()) : 0; | |||
| aOuts = (fInstance->getTotalNumOutputChannels() > 0) ? static_cast<uint32_t>(fInstance->getTotalNumOutputChannels()) : 0; | |||
| params = (fInstance->getNumParameters() > 0) ? static_cast<uint32_t>(fInstance->getNumParameters()) : 0; | |||
| if (fInstance->acceptsMidi()) | |||
| { | |||
| @@ -37,7 +37,7 @@ extern "C" { | |||
| #include "rtmempool/rtmempool-lv2.h" | |||
| } | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| using juce::File; | |||
| @@ -31,7 +31,7 @@ | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #include <linuxsampler/Sampler.h> | |||
| @@ -21,7 +21,7 @@ | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "CarlaNative.h" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| using juce::jmax; | |||
| using juce::String; | |||
| @@ -29,7 +29,7 @@ | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "CarlaPluginUI.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #include <pthread.h> | |||
| @@ -30,10 +30,10 @@ | |||
| #endif | |||
| #include "jackbridge/JackBridge.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
| # include "juce_gui_basics.h" | |||
| # include "juce_gui_basics/juce_gui_basics.h" | |||
| using juce::JUCEApplication; | |||
| using juce::JUCEApplicationBase; | |||
| using juce::Timer; | |||
| @@ -21,7 +21,7 @@ | |||
| #include "CarlaMIDI.h" | |||
| #include "LinkedList.hpp" | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #define URI_CARLA_ATOM_WORKER "http://kxstudio.sf.net/ns/carla/atomWorker" | |||
| @@ -23,7 +23,7 @@ | |||
| #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
| # define USE_JUCE_PROCESSORS | |||
| # include "juce_audio_processors.h" | |||
| # include "juce_audio_processors/juce_audio_processors.h" | |||
| #endif | |||
| #ifdef BUILD_BRIDGE | |||
| @@ -46,14 +46,15 @@ | |||
| #include <iostream> | |||
| #include "juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| #define DISCOVERY_OUT(x, y) std::cout << "\ncarla-discovery::" << x << "::" << y << std::endl; | |||
| using juce::CharPointer_UTF8; | |||
| using juce::File; | |||
| using juce::String; | |||
| using juce::StringArray; | |||
| #define DISCOVERY_OUT(x, y) std::cout << "\ncarla-discovery::" << x << "::" << y << std::endl; | |||
| CARLA_BACKEND_USE_NAMESPACE | |||
| // -------------------------------------------------------------------------- | |||
| @@ -0,0 +1,391 @@ | |||
| #ifndef CARLA_JUCE_APPCONFIG_H_INCLUDED | |||
| #define CARLA_JUCE_APPCONFIG_H_INCLUDED | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Check OS | |||
| #if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) | |||
| # define APPCONFIG_OS_WIN64 | |||
| #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) | |||
| # define APPCONFIG_OS_WIN32 | |||
| #elif defined(__APPLE__) | |||
| # define APPCONFIG_OS_MAC | |||
| #elif defined(__HAIKU__) | |||
| # define APPCONFIG_OS_HAIKU | |||
| #elif defined(__linux__) || defined(__linux) | |||
| # define APPCONFIG_OS_LINUX | |||
| #else | |||
| # warning Unsupported platform! | |||
| #endif | |||
| #if defined(APPCONFIG_OS_WIN32) || defined(APPCONFIG_OS_WIN64) | |||
| # define APPCONFIG_OS_WIN | |||
| #elif defined(APPCONFIG_OS_LINUX) || defined(APPCONFIG_OS_MAC) | |||
| # define APPCONFIG_OS_UNIX | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // always enabled | |||
| #define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 | |||
| #define JUCE_MODULE_AVAILABLE_juce_audio_formats 1 | |||
| #define JUCE_MODULE_AVAILABLE_juce_core 1 | |||
| // always disabled | |||
| #define JUCE_MODULE_AVAILABLE_juce_audio_plugin_client 0 | |||
| #define JUCE_MODULE_AVAILABLE_juce_audio_utils 0 | |||
| #define JUCE_MODULE_AVAILABLE_juce_cryptography 0 | |||
| #define JUCE_MODULE_AVAILABLE_juce_opengl 0 | |||
| #define JUCE_MODULE_AVAILABLE_juce_video 0 | |||
| // conditional | |||
| #if defined(APPCONFIG_OS_MAC) || defined(APPCONFIG_OS_WIN) | |||
| # define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 | |||
| # define JUCE_MODULE_AVAILABLE_juce_audio_processors 1 | |||
| # define JUCE_MODULE_AVAILABLE_juce_data_structures 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 | |||
| #else | |||
| # define JUCE_MODULE_AVAILABLE_juce_audio_devices 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_audio_processors 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_data_structures 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_events 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_graphics 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_gui_basics 0 | |||
| # define JUCE_MODULE_AVAILABLE_juce_gui_extra 0 | |||
| #endif | |||
| // misc | |||
| #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | |||
| #define JUCE_STANDALONE_APPLICATION 0 | |||
| #define JUCE_STRING_UTF_TYPE 8 | |||
| #define JUCE_USE_VFORK 1 | |||
| #if ! (defined(APPCONFIG_OS_MAC) || defined(APPCONFIG_OS_WIN)) | |||
| # define JUCE_MODAL_LOOPS_PERMITTED 0 | |||
| # define JUCE_AUDIO_PROCESSOR_NO_GUI 1 | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // juce_audio_basics | |||
| // nothing here | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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. | |||
| */ | |||
| #ifdef APPCONFIG_OS_WIN | |||
| #define JUCE_ASIO 1 | |||
| #else | |||
| #define JUCE_ASIO 0 | |||
| #endif | |||
| /** Config: JUCE_WASAPI | |||
| Enables WASAPI audio devices (Windows Vista and above). | |||
| */ | |||
| #define JUCE_WASAPI 0 | |||
| /** Config: JUCE_DIRECTSOUND | |||
| Enables DirectSound audio (MS Windows only). | |||
| */ | |||
| #ifdef APPCONFIG_OS_WIN | |||
| #define JUCE_DIRECTSOUND 1 | |||
| #else | |||
| #define JUCE_DIRECTSOUND 0 | |||
| #endif | |||
| /** Config: JUCE_ALSA | |||
| Enables ALSA audio devices (Linux only). | |||
| */ | |||
| #if 0 //APPCONFIG_OS_LINUX | |||
| #define JUCE_ALSA 1 | |||
| #define JUCE_ALSA_MIDI_INPUT_NAME "Carla" | |||
| #define JUCE_ALSA_MIDI_OUTPUT_NAME "Carla" | |||
| #define JUCE_ALSA_MIDI_INPUT_PORT_NAME "Midi In" | |||
| #define JUCE_ALSA_MIDI_OUTPUT_PORT_NAME "Midi Out" | |||
| #else | |||
| #define JUCE_ALSA 0 | |||
| #endif | |||
| /** Config: JUCE_JACK | |||
| Enables JACK audio devices (Linux only). | |||
| */ | |||
| #if 0 //APPCONFIG_OS_LINUX | |||
| #define JUCE_JACK 1 | |||
| #define JUCE_JACK_CLIENT_NAME "Carla" | |||
| #else | |||
| #define JUCE_JACK 0 | |||
| #endif | |||
| //============================================================================= | |||
| /** 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 1 | |||
| /** 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 | |||
| */ | |||
| #ifndef VESTIGE_HEADER | |||
| # define JUCE_PLUGINHOST_VST 1 | |||
| #else | |||
| # define JUCE_PLUGINHOST_VST 0 | |||
| #endif | |||
| /** 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 defined(APPCONFIG_OS_MAC) || defined(APPCONFIG_OS_WIN) | |||
| # 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 | |||
| */ | |||
| #ifdef APPCONFIG_OS_MAC | |||
| # define JUCE_PLUGINHOST_AU 1 | |||
| #else | |||
| # define JUCE_PLUGINHOST_AU 0 | |||
| #endif | |||
| #define JUCE_PLUGINHOST_LADSPA 0 | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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 JUCEApplicationBase::unhandledException() callback. | |||
| */ | |||
| #define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0 | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // juce_data_structures | |||
| // nothing here | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // juce_events | |||
| // nothing here | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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. | |||
| */ | |||
| #define JUCE_USE_COREIMAGE_LOADER 1 | |||
| /** Config: JUCE_USE_DIRECTWRITE | |||
| Enabling this flag means that DirectWrite will be used when available for font | |||
| management and layout. | |||
| */ | |||
| #define JUCE_USE_DIRECTWRITE 1 | |||
| #define JUCE_INCLUDE_PNGLIB_CODE 1 | |||
| #define JUCE_INCLUDE_JPEGLIB_CODE 1 | |||
| #define USE_COREGRAPHICS_RENDERING 1 | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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. | |||
| This will be used as a fallback if JUCE_USE_XRANDR not set or libxrandr cannot be found. | |||
| Note that your users do not need to have Xrandr installed for your JUCE app to run, as | |||
| the availability of Xinerama is queried during runtime. | |||
| */ | |||
| #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. | |||
| */ | |||
| #define JUCE_USE_XSHM 1 | |||
| /** 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. | |||
| */ | |||
| #define JUCE_USE_XCURSOR 1 | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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 | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #endif // CARLA_JUCE_APPCONFIG_H_INCLUDED | |||
| @@ -1,26 +0,0 @@ | |||
| /* | |||
| * Carla Juce setup | |||
| * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_BASICS_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_BASICS_H_INCLUDED | |||
| #include "juce_core.h" | |||
| #include "juce_audio_basics/AppConfig.h" | |||
| #include "juce_audio_basics/juce_audio_basics.h" | |||
| #endif // CARLA_JUCE_AUDIO_BASICS_H_INCLUDED | |||
| @@ -1,14 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| Build options for juce_audio_basics static library | |||
| ============================================================================== | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||
| #include "../juce_core/AppConfig.h" | |||
| #endif // CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||
| @@ -10,7 +10,7 @@ include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_BASICS_FLAGS) -w | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_BASICS_FLAGS) -I.. | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -99,7 +99,7 @@ public: | |||
| class Int8 | |||
| { | |||
| public: | |||
| inline Int8 (void* d) noexcept : data (static_cast <int8*> (d)) {} | |||
| inline Int8 (void* d) noexcept : data (static_cast<int8*> (d)) {} | |||
| inline void advance() noexcept { ++data; } | |||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||
| @@ -124,7 +124,7 @@ public: | |||
| class UInt8 | |||
| { | |||
| public: | |||
| inline UInt8 (void* d) noexcept : data (static_cast <uint8*> (d)) {} | |||
| inline UInt8 (void* d) noexcept : data (static_cast<uint8*> (d)) {} | |||
| inline void advance() noexcept { ++data; } | |||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||
| @@ -149,7 +149,7 @@ public: | |||
| class Int16 | |||
| { | |||
| public: | |||
| inline Int16 (void* d) noexcept : data (static_cast <uint16*> (d)) {} | |||
| inline Int16 (void* d) noexcept : data (static_cast<uint16*> (d)) {} | |||
| inline void advance() noexcept { ++data; } | |||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||
| @@ -174,7 +174,7 @@ public: | |||
| class Int24 | |||
| { | |||
| public: | |||
| inline Int24 (void* d) noexcept : data (static_cast <char*> (d)) {} | |||
| 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; } | |||
| @@ -199,7 +199,7 @@ public: | |||
| class Int32 | |||
| { | |||
| public: | |||
| inline Int32 (void* d) noexcept : data (static_cast <uint32*> (d)) {} | |||
| inline Int32 (void* d) noexcept : data (static_cast<uint32*> (d)) {} | |||
| inline void advance() noexcept { ++data; } | |||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||
| @@ -245,7 +245,7 @@ public: | |||
| class Float32 | |||
| { | |||
| public: | |||
| inline Float32 (void* d) noexcept : data (static_cast <float*> (d)) {} | |||
| inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {} | |||
| inline void advance() noexcept { ++data; } | |||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||
| @@ -318,7 +318,7 @@ public: | |||
| { | |||
| public: | |||
| typedef const void VoidType; | |||
| static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast <void*> (v); } | |||
| static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); } | |||
| enum { isConst = 1 }; | |||
| }; | |||
| #endif | |||
| @@ -815,11 +815,13 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| union {float f; uint32 i;} signMask; | |||
| union { float f; uint32 i; } signMask; | |||
| signMask.i = 0x7fffffffUL; | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mask = Mode::load1 (signMask.f);) | |||
| ignoreUnused (signMask); | |||
| #endif | |||
| } | |||
| @@ -834,6 +836,8 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mask = Mode::load1 (signMask.d);) | |||
| ignoreUnused (signMask); | |||
| #endif | |||
| } | |||
| @@ -1001,7 +1005,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||
| if (FloatVectorHelpers::isSSE2Available()) | |||
| _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); | |||
| #endif | |||
| (void) shouldEnable; | |||
| ignoreUnused (shouldEnable); | |||
| } | |||
| //============================================================================== | |||
| @@ -248,7 +248,7 @@ void FFT::performRealOnlyInverseTransform (float* d) const noexcept | |||
| if (scratchSize < maxFFTScratchSpaceToAlloca) | |||
| { | |||
| performRealOnlyForwardTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
| performRealOnlyInverseTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
| } | |||
| else | |||
| { | |||
| @@ -0,0 +1,97 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED | |||
| #define JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Utility class for linearly smoothed values like volume etc. that should | |||
| not change abruptly but as a linear ramp, to avoid audio glitches. | |||
| */ | |||
| //============================================================================== | |||
| template<typename FloatType> | |||
| class JUCE_API LinearSmoothedValue | |||
| { | |||
| public: | |||
| /** Constructor. */ | |||
| LinearSmoothedValue() noexcept | |||
| : currentValue (0), target (0), step (0), countdown (0), stepsToTarget (0) | |||
| { | |||
| } | |||
| /** Constructor. */ | |||
| LinearSmoothedValue (FloatType initialValue) noexcept | |||
| : currentValue (initialValue), target (initialValue), step (0), countdown (0), stepsToTarget (0) | |||
| { | |||
| } | |||
| //========================================================================== | |||
| /** Reset to a new sample rate and ramp length. */ | |||
| void reset (double sampleRate, double rampLengthInSeconds) noexcept | |||
| { | |||
| jassert (sampleRate > 0 && rampLengthInSeconds >= 0); | |||
| stepsToTarget = (int) std::floor (rampLengthInSeconds * sampleRate); | |||
| currentValue = target; | |||
| countdown = 0; | |||
| } | |||
| //========================================================================== | |||
| /** Set a new target value. */ | |||
| void setValue (FloatType newValue) noexcept | |||
| { | |||
| if (target != newValue) | |||
| { | |||
| target = newValue; | |||
| countdown = stepsToTarget; | |||
| if (countdown <= 0) | |||
| currentValue = target; | |||
| else | |||
| step = (target - currentValue) / (FloatType) countdown; | |||
| } | |||
| } | |||
| //========================================================================== | |||
| /** Compute the next value. */ | |||
| FloatType getNextValue() noexcept | |||
| { | |||
| if (countdown <= 0) | |||
| return target; | |||
| --countdown; | |||
| currentValue += step; | |||
| return currentValue; | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| FloatType currentValue, target, step; | |||
| int countdown, stepsToTarget; | |||
| }; | |||
| #endif // JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED | |||
| @@ -306,54 +306,6 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE (AllPassFilter) | |||
| }; | |||
| //============================================================================== | |||
| class LinearSmoothedValue | |||
| { | |||
| public: | |||
| LinearSmoothedValue() noexcept | |||
| : currentValue (0), target (0), step (0), countdown (0), stepsToTarget (0) | |||
| { | |||
| } | |||
| void reset (double sampleRate, double fadeLengthSeconds) noexcept | |||
| { | |||
| jassert (sampleRate > 0 && fadeLengthSeconds >= 0); | |||
| stepsToTarget = (int) std::floor (fadeLengthSeconds * sampleRate); | |||
| currentValue = target; | |||
| countdown = 0; | |||
| } | |||
| void setValue (float newValue) noexcept | |||
| { | |||
| if (target != newValue) | |||
| { | |||
| target = newValue; | |||
| countdown = stepsToTarget; | |||
| if (countdown <= 0) | |||
| currentValue = target; | |||
| else | |||
| step = (target - currentValue) / (float) countdown; | |||
| } | |||
| } | |||
| float getNextValue() noexcept | |||
| { | |||
| if (countdown <= 0) | |||
| return target; | |||
| --countdown; | |||
| currentValue += step; | |||
| return currentValue; | |||
| } | |||
| private: | |||
| float currentValue, target, step; | |||
| int countdown, stepsToTarget; | |||
| JUCE_DECLARE_NON_COPYABLE (LinearSmoothedValue) | |||
| }; | |||
| //============================================================================== | |||
| enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; | |||
| @@ -363,7 +315,7 @@ private: | |||
| CombFilter comb [numChannels][numCombs]; | |||
| AllPassFilter allPass [numChannels][numAllPasses]; | |||
| LinearSmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; | |||
| LinearSmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) | |||
| }; | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #if defined (JUCE_AUDIO_BASICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
| #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 | |||
| @@ -31,16 +31,13 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| // Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
| // and your header search path must make it accessible to the module's files. | |||
| #include "AppConfig.h" | |||
| #include "juce_audio_basics.h" | |||
| #if JUCE_MINGW && ! defined (__SSE2__) | |||
| #define JUCE_USE_SSE_INTRINSICS 0 | |||
| #endif | |||
| #if JUCE_MINGW | |||
| #if JUCE_MINGW && ! defined (alloca) | |||
| #define alloca __builtin_alloca | |||
| #endif | |||
| @@ -90,6 +87,16 @@ namespace juce | |||
| #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_MPEZone.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 "sources/juce_BufferingAudioSource.cpp" | |||
| #include "sources/juce_ChannelRemappingAudioSource.cpp" | |||
| #include "sources/juce_IIRFilterAudioSource.cpp" | |||
| @@ -42,12 +42,23 @@ namespace juce | |||
| #include "effects/juce_IIRFilterOld.h" | |||
| #include "effects/juce_LagrangeInterpolator.h" | |||
| #include "effects/juce_FFT.h" | |||
| #include "effects/juce_LinearSmoothedValue.h" | |||
| #include "effects/juce_Reverb.h" | |||
| #include "midi/juce_MidiMessage.h" | |||
| #include "midi/juce_MidiBuffer.h" | |||
| #include "midi/juce_MidiMessageSequence.h" | |||
| #include "midi/juce_MidiFile.h" | |||
| #include "midi/juce_MidiKeyboardState.h" | |||
| #include "midi/juce_MidiRPN.h" | |||
| #include "mpe/juce_MPEValue.h" | |||
| #include "mpe/juce_MPENote.h" | |||
| #include "mpe/juce_MPEZone.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 "sources/juce_AudioSource.h" | |||
| #include "sources/juce_PositionableAudioSource.h" | |||
| #include "sources/juce_BufferingAudioSource.h" | |||
| @@ -254,12 +254,12 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||
| if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
| { | |||
| size_t size = data.getSize(); | |||
| const uint8* d = static_cast <const uint8*> (data.getData()); | |||
| const uint8* 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())); | |||
| size -= (size_t) (d - static_cast<const uint8*> (data.getData())); | |||
| int track = 0; | |||
| @@ -672,7 +672,7 @@ bool MidiMessage::isTextMetaEvent() const noexcept | |||
| String MidiMessage::getTextFromTextMetaEvent() const | |||
| { | |||
| const char* const textData = reinterpret_cast <const char*> (getMetaEventData()); | |||
| const char* const textData = reinterpret_cast<const char*> (getMetaEventData()); | |||
| return String (CharPointer_UTF8 (textData), | |||
| CharPointer_UTF8 (textData + getMetaEventLength())); | |||
| } | |||
| @@ -982,7 +982,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||
| return s; | |||
| } | |||
| return String::empty; | |||
| return String(); | |||
| } | |||
| double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept | |||
| @@ -0,0 +1,374 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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") {} | |||
| 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") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("generating RPN/NRPN"); | |||
| { | |||
| { | |||
| MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); | |||
| expectContainsRPN (buffer, 1, 23, 1337, true, true); | |||
| } | |||
| { | |||
| MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); | |||
| expectContainsRPN (buffer, 16, 101, 34, false, false); | |||
| } | |||
| { | |||
| MidiRPNMessage message = { 16, 101, 34, false, false }; | |||
| MidiBuffer buffer = MidiRPNGenerator::generate (message); | |||
| expectContainsRPN (buffer, message); | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| void expectContainsRPN (const MidiBuffer& midiBuffer, | |||
| int channel, | |||
| int parameterNumber, | |||
| int value, | |||
| bool isNRPN, | |||
| bool is14BitValue) | |||
| { | |||
| MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; | |||
| expectContainsRPN (midiBuffer, expected); | |||
| } | |||
| //========================================================================== | |||
| void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | |||
| { | |||
| MidiBuffer::Iterator iter (midiBuffer); | |||
| MidiMessage midiMessage; | |||
| MidiRPNMessage result = MidiRPNMessage(); | |||
| MidiRPNDetector detector; | |||
| int samplePosition; // not actually used, so no need to initialise. | |||
| while (iter.getNextEvent (midiMessage, samplePosition)) | |||
| { | |||
| if (detector.parseControllerMessage (midiMessage.getChannel(), | |||
| midiMessage.getControllerNumber(), | |||
| midiMessage.getControllerValue(), | |||
| result)) | |||
| break; | |||
| } | |||
| expectEquals (result.channel, expected.channel); | |||
| expectEquals (result.parameterNumber, expected.parameterNumber); | |||
| expectEquals (result.value, expected.value); | |||
| expect (result.isNRPN == expected.isNRPN), | |||
| expect (result.is14BitValue == expected.is14BitValue); | |||
| } | |||
| }; | |||
| static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,152 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
| #define JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
| //========================================================================== | |||
| /** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | |||
| parameter number) message. | |||
| */ | |||
| 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). | |||
| */ | |||
| 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. | |||
| */ | |||
| class JUCE_API MidiRPNGenerator | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| /** Generates a MIDI sequence representing the given RPN or NRPN message. */ | |||
| static MidiBuffer generate (MidiRPNMessage message); | |||
| //========================================================================== | |||
| /** Generates a MIDI sequence representing an RPN or NRPN message with the | |||
| given parameters. | |||
| @param channel The MIDI channel of the RPN/NRPN message. | |||
| @param parameterNumber The parameter number, in the range 0 to 16383. | |||
| @param value The parameter value, in the range 0 to 16383, or | |||
| in the range 0 to 127 if sendAs14BitValue is false. | |||
| @param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). | |||
| @param use14BitValue If true (default), the value will have 14-bit precision | |||
| (two MIDI bytes). If false, instead the value will have | |||
| 7-bit presision (a single MIDI byte). | |||
| */ | |||
| static MidiBuffer generate (int channel, | |||
| int parameterNumber, | |||
| int value, | |||
| bool isNRPN = false, | |||
| bool use14BitValue = true); | |||
| }; | |||
| #endif // JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
| @@ -0,0 +1,414 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEINSTRUMENT_H_INCLUDED | |||
| #define JUCE_MPEINSTRUMENT_H_INCLUDED | |||
| //============================================================================== | |||
| /* | |||
| This class represents an instrument handling MPE. | |||
| It has an MPE zone layout and maintans a state of currently | |||
| active (playing) notes and the values of their dimensions of expression. | |||
| You can trigger and modulate notes: | |||
| - by passing MIDI messages with the method processNextMidiEvent; | |||
| - by directly calling the methods noteOn, noteOff etc. | |||
| The class implements the channel and note management logic specified in | |||
| MPE. If you pass it a message, it will know what notes on what | |||
| channels (if any) should be affected by that message. | |||
| The class has a Listener class with the three callbacks MPENoteAdded, | |||
| MPENoteChanged, and MPENoteFinished. Implement such a | |||
| Listener class to react to note changes and trigger some functionality for | |||
| your application that depends on the MPE note state. | |||
| For example, you can use this class to write an MPE visualiser. | |||
| If you want to write a real-time audio synth with MPE functionality, | |||
| you should instead use the classes MPESynthesiserBase, which adds | |||
| the ability to render audio and to manage voices. | |||
| @see MPENote, MPEZoneLayout, MPESynthesiser | |||
| */ | |||
| class JUCE_API MPEInstrument | |||
| { | |||
| public: | |||
| /** Constructor. | |||
| This will construct an MPE instrument with initially no MPE 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 isNoteChannel (int midiChannel) const noexcept; | |||
| /** Returns true if the given MIDI channel (1-16) is a master channel in any | |||
| of the MPEInstrument's MPE zones; false otherwise. | |||
| When in legacy mode, this will always return false. | |||
| */ | |||
| bool isMasterChannel (int midiChannel) const noexcept; | |||
| //========================================================================== | |||
| /** The MPE note tracking mode. In case there is more than one note playing | |||
| simultaneously on the same MIDI channel, this determines which of these | |||
| notes will be modulated by an incoming MPE message on that channel | |||
| (pressure, pitchbend, or timbre). | |||
| The default is lastNotePlayedOnChannel. | |||
| */ | |||
| enum TrackingMode | |||
| { | |||
| lastNotePlayedOnChannel, //! The most recent note on the channel that is still played (key down and/or sustained) | |||
| lowestNoteOnChannel, //! The lowest note (by initialNote) on the channel with the note key still down | |||
| highestNoteOnChannel, //! The highest note (by initialNote) on the channel with the note key still down | |||
| allNotesOnChannel //! All notes on the channel (key down and/or sustained) | |||
| }; | |||
| /** Set the MPE tracking mode for the pressure dimension. */ | |||
| void setPressureTrackingMode (TrackingMode modeToUse); | |||
| /** Set the MPE tracking mode for the pitchbend dimension. */ | |||
| void setPitchbendTrackingMode (TrackingMode modeToUse); | |||
| /** Set the MPE tracking mode for the timbre dimension. */ | |||
| void setTimbreTrackingMode (TrackingMode modeToUse); | |||
| //========================================================================== | |||
| /** Process a MIDI message and trigger the appropriate method calls | |||
| (noteOn, noteOff etc.) | |||
| You can override this method if you need some special MIDI message | |||
| treatment on top of the standard MPE logic implemented here. | |||
| */ | |||
| virtual void processNextMidiEvent (const MidiMessage& message); | |||
| //========================================================================== | |||
| /** Request a note-on on the given channel, with the given initial note | |||
| number and velocity. | |||
| If the message arrives on a valid note channel, this will create a | |||
| new MPENote and call the noteAdded callback. | |||
| */ | |||
| virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity); | |||
| /** Request a note-off. If there is a matching playing note, this will | |||
| release the note (except if it is sustained by a sustain or sostenuto | |||
| pedal) and call the noteReleased callback. | |||
| */ | |||
| virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity); | |||
| /** Request a pitchbend on the given channel with the given value (in units | |||
| of MIDI pitchwheel position). | |||
| Internally, this will determine whether the pitchwheel move is a | |||
| per-note pitchbend or a master pitchbend (depending on midiChannel), | |||
| take the correct per-note or master pitchbend range of the affected MPE | |||
| zone, and apply the resulting pitchbend to the affected note(s) (if any). | |||
| */ | |||
| virtual void pitchbend (int midiChannel, MPEValue pitchbend); | |||
| /** Request a pressure change on the given channel with the given value. | |||
| This will modify the pressure dimension of the note currently held down | |||
| on this channel (if any). If the channel is a zone master channel, | |||
| the pressure change will be broadcast to all notes in this zone. | |||
| */ | |||
| virtual void pressure (int midiChannel, MPEValue value); | |||
| /** Request a third dimension (timbre) change on the given channel with the | |||
| given value. | |||
| This will modify the timbre dimension of the note currently held down | |||
| on this channel (if any). If the channel is a zone master channel, | |||
| the timbre change will be broadcast to all notes in this zone. | |||
| */ | |||
| virtual void timbre (int midiChannel, MPEValue value); | |||
| /** Request a sustain pedal press or release. If midiChannel is a zone's | |||
| master channel, this will act on all notes in that zone; otherwise, | |||
| nothing will happen. | |||
| */ | |||
| virtual void sustainPedal (int midiChannel, bool isDown); | |||
| /** Request a sostenuto pedal press or release. If midiChannel is a zone's | |||
| master channel, this will act on all notes in that zone; otherwise, | |||
| nothing will happen. | |||
| */ | |||
| virtual void sostenutoPedal (int midiChannel, bool isDown); | |||
| /** Discard all currently playing notes. | |||
| This will also call the noteReleased listener callback for all of them. | |||
| */ | |||
| void releaseAllNotes(); | |||
| //========================================================================== | |||
| /** Returns the number of MPE notes currently played by the | |||
| instrument. | |||
| */ | |||
| int getNumPlayingNotes() const noexcept; | |||
| /** Returns the note at the given index. If there is no such note, returns | |||
| an invalid MPENote. The notes are sorted such that the most recently | |||
| added note is the last element. | |||
| */ | |||
| MPENote getNote (int index) const noexcept; | |||
| /** Returns the note currently playing on the given midiChannel with the | |||
| specified initial MIDI note number, if there is such a note. | |||
| Otherwise, this returns an invalid MPENote | |||
| (check with note.isValid() before use!) | |||
| */ | |||
| MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; | |||
| /** Returns the most recent note that is playing on the given midiChannel | |||
| (this will be the note which has received the most recent note-on without | |||
| a corresponding note-off), if there is such a note. | |||
| Otherwise, this returns an invalid MPENote | |||
| (check with note.isValid() before use!) | |||
| */ | |||
| MPENote getMostRecentNote (int midiChannel) const noexcept; | |||
| /** Returns the most recent note that is not the note passed in. | |||
| If there is no such note, this returns an invalid MPENote | |||
| (check with note.isValid() before use!) | |||
| This helper method might be useful for some custom voice handling algorithms. | |||
| */ | |||
| MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; | |||
| //========================================================================== | |||
| /** Derive from this class to be informed about any changes in the expressive | |||
| MIDI notes played by this instrument. | |||
| Note: This listener type receives its callbacks immediately, and not | |||
| via the message thread (so you might be for example in the MIDI thread). | |||
| Therefore you should never do heavy work such as graphics rendering etc. | |||
| inside those callbacks. | |||
| */ | |||
| class Listener | |||
| { | |||
| public: | |||
| /** Constructor. */ | |||
| Listener(); | |||
| /** Destructor. */ | |||
| virtual ~Listener(); | |||
| /** Implement this callback to be informed whenever a new expressive | |||
| MIDI note is triggered. | |||
| */ | |||
| virtual void noteAdded (MPENote newNote) = 0; | |||
| /** Implement this callback to be informed whenever a currently | |||
| playing MPE note's pressure value changes. | |||
| */ | |||
| virtual void notePressureChanged (MPENote changedNote) = 0; | |||
| /** Implement this callback to be informed whenever a currently | |||
| playing MPE note's pitchbend value changes. | |||
| Note: This can happen if the note itself is bent, if there is a | |||
| master channel pitchbend event, or if both occur simultaneously. | |||
| Call MPENote::getFrequencyInHertz to get the effective note frequency. | |||
| */ | |||
| virtual void notePitchbendChanged (MPENote changedNote) = 0; | |||
| /** Implement this callback to be informed whenever a currently | |||
| playing MPE note's timbre value changes. | |||
| */ | |||
| virtual void noteTimbreChanged (MPENote changedNote) = 0; | |||
| /** Implement this callback to be informed whether a currently playing | |||
| MPE note's key state (whether the key is down and/or the note is | |||
| sustained) has changed. | |||
| Note: if the key state changes to MPENote::off, noteReleased is | |||
| called instead. | |||
| */ | |||
| virtual void noteKeyStateChanged (MPENote changedNote) = 0; | |||
| /** Implement this callback to be informed whenever an MPE note | |||
| is released (either by a note-off message, or by a sustain/sostenuto | |||
| pedal release for a note that already received a note-off), | |||
| and should therefore stop playing. | |||
| */ | |||
| virtual void noteReleased (MPENote finishedNote) = 0; | |||
| }; | |||
| //========================================================================== | |||
| /** Adds a listener. */ | |||
| void addListener (Listener* const listenerToAdd) noexcept; | |||
| /** Removes a listener. */ | |||
| void removeListener (Listener* const listenerToRemove) noexcept; | |||
| //========================================================================== | |||
| /** Puts the instrument into legacy mode. | |||
| As a side effect, this will discard all currently playing notes, | |||
| and call noteReleased for all of them. | |||
| This special zone layout mode is for backwards compatibility with | |||
| non-MPE MIDI devices. In this mode, the instrument will ignore the | |||
| current MPE zone layout. It will instead take a range of MIDI channels | |||
| (default: all channels 1-16) and treat them as note channels, with no | |||
| master channel. MIDI channels outside of this range will be ignored. | |||
| @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||
| Must be between 0 and 96, otherwise behaviour is undefined. | |||
| The default pitchbend range in legacy mode is +/- 2 semitones. | |||
| @param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||
| The default is to use all MIDI channels (1-16). | |||
| To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||
| */ | |||
| void enableLegacyMode (int pitchbendRange = 2, | |||
| Range<int> channelRange = Range<int> (1, 17)); | |||
| /** Returns true if the instrument is in legacy mode, false otherwise. */ | |||
| bool isLegacyModeEnabled() const noexcept; | |||
| /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
| Range<int> getLegacyModeChannelRange() const noexcept; | |||
| /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
| void setLegacyModeChannelRange (Range<int> channelRange); | |||
| /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
| int getLegacyModePitchbendRange() const noexcept; | |||
| /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
| void setLegacyModePitchbendRange (int pitchbendRange); | |||
| protected: | |||
| //========================================================================== | |||
| /** This method defines what initial pitchbend value should be used for newly | |||
| triggered notes. The default is to use the last pitchbend value | |||
| that has been received on the same MIDI channel (or no pitchbend | |||
| if no pitchbend messages have been received so far). | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| /** This method defines what initial pressure value should be used for newly | |||
| triggered notes. The default is to re-use the note-on velocity value. | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialPressureForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| /** This method defines what initial timbre value should be used for newly | |||
| triggered notes. The default is to use the last timbre value that has | |||
| that has been received on the same MIDI channel (or a neutral centred value | |||
| if no pitchbend messages have been received so far). | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| private: | |||
| //========================================================================== | |||
| CriticalSection lock; | |||
| Array<MPENote> notes; | |||
| MPEZoneLayout zoneLayout; | |||
| ListenerList<Listener> listeners; | |||
| uint8 lastPressureLowerBitReceivedOnChannel[16]; | |||
| uint8 lastTimbreLowerBitReceivedOnChannel[16]; | |||
| bool isNoteChannelSustained[16]; | |||
| struct LegacyMode | |||
| { | |||
| bool isEnabled; | |||
| Range<int> channelRange; | |||
| int pitchbendRange; | |||
| }; | |||
| struct MPEDimension | |||
| { | |||
| MPEDimension() noexcept : trackingMode (lastNotePlayedOnChannel) {} | |||
| TrackingMode trackingMode; | |||
| MPEValue lastValueReceivedOnChannel[16]; | |||
| MPEValue MPENote::* value; | |||
| MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } | |||
| }; | |||
| LegacyMode legacyMode; | |||
| MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | |||
| void updateDimension (int midiChannel, MPEDimension&, MPEValue); | |||
| void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | |||
| void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||
| void callListenersDimensionChanged (MPENote&, MPEDimension&); | |||
| void processMidiNoteOnMessage (const MidiMessage&); | |||
| void processMidiNoteOffMessage (const MidiMessage&); | |||
| void processMidiPitchWheelMessage (const MidiMessage&); | |||
| void processMidiChannelPressureMessage (const MidiMessage&); | |||
| void processMidiControllerMessage (const MidiMessage&); | |||
| void processMidiAllNotesOffMessage (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); | |||
| MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept; | |||
| MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept; | |||
| MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept; | |||
| MPENote* getHighestNotePtr (int midiChannel) const noexcept; | |||
| MPENote* getLowestNotePtr (int midiChannel) const noexcept; | |||
| void updateNoteTotalPitchbend (MPENote&); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument) | |||
| }; | |||
| #endif // JUCE_MPE_H_INCLUDED | |||
| @@ -0,0 +1,198 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MidiBuffer MPEMessages::addZone (MPEZone zone) | |||
| { | |||
| MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | |||
| zoneLayoutMessagesRpnNumber, | |||
| zone.getNumNoteChannels(), | |||
| false, false)); | |||
| buffer.addEvents (perNotePitchbendRange (zone), 0, -1, 0); | |||
| buffer.addEvents (masterPitchbendRange (zone), 0, -1, 0); | |||
| return buffer; | |||
| } | |||
| MidiBuffer MPEMessages::perNotePitchbendRange (MPEZone zone) | |||
| { | |||
| return MidiRPNGenerator::generate (zone.getFirstNoteChannel(), 0, | |||
| zone.getPerNotePitchbendRange(), | |||
| false, false); | |||
| } | |||
| MidiBuffer MPEMessages::masterPitchbendRange (MPEZone zone) | |||
| { | |||
| return MidiRPNGenerator::generate (zone.getMasterChannel(), 0, | |||
| zone.getMasterPitchbendRange(), | |||
| false, false); | |||
| } | |||
| MidiBuffer MPEMessages::clearAllZones() | |||
| { | |||
| return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 16, false, false); | |||
| } | |||
| MidiBuffer MPEMessages::setZoneLayout (const MPEZoneLayout& layout) | |||
| { | |||
| MidiBuffer buffer; | |||
| buffer.addEvents (clearAllZones(), 0, -1, 0); | |||
| for (int i = 0; i < layout.getNumZones(); ++i) | |||
| buffer.addEvents (addZone (*layout.getZoneByIndex (i)), 0, -1, 0); | |||
| return buffer; | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class MPEMessagesTests : public UnitTest | |||
| { | |||
| public: | |||
| MPEMessagesTests() : UnitTest ("MPEMessages class") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("add zone"); | |||
| { | |||
| { | |||
| MidiBuffer buffer = MPEMessages::addZone (MPEZone (1, 7)); | |||
| const uint8 expectedBytes[] = | |||
| { | |||
| 0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 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::addZone (MPEZone (11, 5, 96, 0)); | |||
| const uint8 expectedBytes[] = | |||
| { | |||
| 0xbb, 0x64, 0x06, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x05, // set up zone | |||
| 0xbb, 0x64, 0x00, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x60, // per-note pbrange (custom) | |||
| 0xba, 0x64, 0x00, 0xba, 0x65, 0x00, 0xba, 0x06, 0x00 // master pbrange (custom) | |||
| }; | |||
| testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
| } | |||
| } | |||
| beginTest ("set per-note pitchbend range"); | |||
| { | |||
| MPEZone zone (3, 7, 96); | |||
| MidiBuffer buffer = MPEMessages::perNotePitchbendRange (zone); | |||
| const uint8 expectedBytes[] = { 0xb3, 0x64, 0x00, 0xb3, 0x65, 0x00, 0xb3, 0x06, 0x60 }; | |||
| testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
| } | |||
| beginTest ("set master pitchbend range"); | |||
| { | |||
| MPEZone zone (3, 7, 48, 60); | |||
| MidiBuffer buffer = MPEMessages::masterPitchbendRange (zone); | |||
| const uint8 expectedBytes[] = { 0xb2, 0x64, 0x00, 0xb2, 0x65, 0x00, 0xb2, 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, 0x10 }; | |||
| testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
| } | |||
| beginTest ("set complete state"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| layout.addZone (MPEZone (1, 7, 96, 0)); | |||
| layout.addZone (MPEZone (9, 7)); | |||
| layout.addZone (MPEZone (5, 3)); | |||
| layout.addZone (MPEZone (5, 4)); | |||
| layout.addZone (MPEZone (6, 4)); | |||
| MidiBuffer buffer = MPEMessages::setZoneLayout (layout); | |||
| const uint8 expectedBytes[] = { | |||
| 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10, // clear all zones | |||
| 0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x03, // set zone 1 (1, 3) | |||
| 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) | |||
| 0xb6, 0x64, 0x06, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x04, // set zone 2 (6, 4) | |||
| 0xb6, 0x64, 0x00, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x30, // per-note pbrange (default = 48) | |||
| 0xb5, 0x64, 0x00, 0xb5, 0x65, 0x00, 0xb5, 0x06, 0x02 // master pbrange (default = 2) | |||
| }; | |||
| testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
| } | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) | |||
| { | |||
| uint8 actualBytes[128] = { 0 }; | |||
| extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes)); | |||
| expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); | |||
| } | |||
| //========================================================================== | |||
| void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) | |||
| { | |||
| std::size_t pos = 0; | |||
| MidiBuffer::Iterator iter (midiBuffer); | |||
| MidiMessage midiMessage; | |||
| int samplePosition; // Note: not actually used, so no need to initialise. | |||
| while (iter.getNextEvent (midiMessage, samplePosition)) | |||
| { | |||
| const uint8* data = midiMessage.getRawData(); | |||
| std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize(); | |||
| if (pos + dataSize > maxBytes) | |||
| return; | |||
| std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); | |||
| pos += dataSize; | |||
| } | |||
| } | |||
| }; | |||
| static MPEMessagesTests MPEMessagesUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,96 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEMESSAGES_H_INCLUDED | |||
| #define JUCE_MPEMESSAGES_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| This helper class contains the necessary helper functions to generate | |||
| MIDI messages that are exclusive to MPE, such as defining | |||
| 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, MPEZone | |||
| */ | |||
| class JUCE_API MPEMessages | |||
| { | |||
| public: | |||
| /** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
| MIDI device, will define a new MPE zone. | |||
| */ | |||
| static MidiBuffer addZone (MPEZone zone); | |||
| /** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
| MIDI device, will change the per-note pitchbend range of an | |||
| existing MPE zone. | |||
| */ | |||
| static MidiBuffer perNotePitchbendRange (MPEZone zone); | |||
| /** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
| MIDI device, will change the master pitchbend range of an | |||
| existing MPE zone. | |||
| */ | |||
| static MidiBuffer masterPitchbendRange (MPEZone zone); | |||
| /** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
| MIDI device, will erase all currently defined MPE zones. | |||
| */ | |||
| static MidiBuffer clearAllZones(); | |||
| /** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
| MIDI device, will reset the whole MPE zone layout of the | |||
| device to the laoyut passed in. This will first clear all currently | |||
| defined MPE zones, then add all zones contained in the | |||
| passed-in zone layout, and set their per-note and master pitchbend | |||
| ranges to their current values. | |||
| */ | |||
| static MidiBuffer setZoneLayout (const MPEZoneLayout& layout); | |||
| /** The RPN number used for MPE zone layout messages. | |||
| Note: This number can change in later versions of MPE. | |||
| 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; | |||
| }; | |||
| #endif // JUCE_MPEMESSAGES_H_INCLUDED | |||
| @@ -0,0 +1,132 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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_), | |||
| timbre (timbre_), | |||
| noteOffVelocity (MPEValue::minValue()), | |||
| keyState (keyState_) | |||
| { | |||
| jassert (keyState != MPENote::off); | |||
| jassert (isValid()); | |||
| } | |||
| MPENote::MPENote() noexcept | |||
| : noteID (0), | |||
| midiChannel (0), | |||
| initialNote (0), | |||
| noteOnVelocity (MPEValue::minValue()), | |||
| pitchbend (MPEValue::centreValue()), | |||
| pressure (MPEValue::centreValue()), | |||
| timbre (MPEValue::centreValue()), | |||
| noteOffVelocity (MPEValue::minValue()), | |||
| keyState (MPENote::off) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| bool MPENote::isValid() const noexcept | |||
| { | |||
| return midiChannel > 0 && midiChannel <= 16 && initialNote >= 0 && initialNote <= 127; | |||
| } | |||
| //============================================================================== | |||
| double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept | |||
| { | |||
| double 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") {} | |||
| //========================================================================== | |||
| void runTest() override | |||
| { | |||
| beginTest ("getFrequencyInHertz"); | |||
| { | |||
| MPENote note; | |||
| note.initialNote = 60; | |||
| note.totalPitchbendInSemitones = -0.5; | |||
| expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); | |||
| } | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| void expectEqualsWithinOneCent (double frequencyInHertzActual, | |||
| double frequencyInHertzExpected) | |||
| { | |||
| double ratio = frequencyInHertzActual / frequencyInHertzExpected; | |||
| double oneCent = 1.0005946; | |||
| expect (ratio < oneCent); | |||
| expect (ratio > 1.0 / oneCent); | |||
| } | |||
| }; | |||
| static MPENoteTests MPENoteUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,180 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPENOTE_H_INCLUDED | |||
| #define JUCE_MPENOTE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| */ | |||
| struct JUCE_API MPENote | |||
| { | |||
| //========================================================================== | |||
| enum KeyState | |||
| { | |||
| off = 0, | |||
| keyDown = 1, | |||
| sustained = 2, | |||
| keyDownAndSustained = 3 | |||
| }; | |||
| //========================================================================== | |||
| /** Constructor. | |||
| @param midiChannel The MIDI channel of the note, between 2 and 16. | |||
| (Channel 1 can never be a note channel 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; | |||
| /** The MIDI channel which this note uses. | |||
| This should never change during the lifetime of an MPENote object. | |||
| */ | |||
| uint8 midiChannel; | |||
| /** 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; | |||
| //========================================================================== | |||
| // 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; | |||
| /** 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; | |||
| /** Current pressure with which the note is held down. | |||
| This dimension can be modulated while the note sounds. | |||
| */ | |||
| MPEValue pressure; | |||
| /** Current value of the note's third expressive dimension, tyically | |||
| encoding some kind of timbre parameter. | |||
| This dimension can be modulated while the note sounds. | |||
| */ | |||
| MPEValue timbre; | |||
| /** 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; | |||
| //========================================================================== | |||
| /** 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; | |||
| //========================================================================== | |||
| /** Returns the current frequency of the note in Hertz. This is the a 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; | |||
| }; | |||
| #endif // JUCE_MPENOTE_H_INCLUDED | |||
| @@ -0,0 +1,356 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MPESynthesiser::MPESynthesiser() | |||
| { | |||
| } | |||
| MPESynthesiser::MPESynthesiser (MPEInstrument* instrument) : MPESynthesiserBase (instrument) | |||
| { | |||
| } | |||
| MPESynthesiser::~MPESynthesiser() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->currentlyPlayingNote = noteToStart; | |||
| voice->noteStarted(); | |||
| } | |||
| void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->currentlyPlayingNote = noteToStop; | |||
| voice->noteStopped (allowTailOff); | |||
| } | |||
| //============================================================================== | |||
| void MPESynthesiser::noteAdded (MPENote newNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| if (MPESynthesiserVoice* voice = findFreeVoice (newNote, shouldStealVoices)) | |||
| startVoice (voice, newNote); | |||
| } | |||
| void MPESynthesiser::notePressureChanged (MPENote changedNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isCurrentlyPlayingNote (changedNote)) | |||
| { | |||
| voice->currentlyPlayingNote = changedNote; | |||
| voice->notePressureChanged(); | |||
| } | |||
| } | |||
| } | |||
| void MPESynthesiser::notePitchbendChanged (MPENote changedNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isCurrentlyPlayingNote (changedNote)) | |||
| { | |||
| voice->currentlyPlayingNote = changedNote; | |||
| voice->notePitchbendChanged(); | |||
| } | |||
| } | |||
| } | |||
| void MPESynthesiser::noteTimbreChanged (MPENote changedNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isCurrentlyPlayingNote (changedNote)) | |||
| { | |||
| voice->currentlyPlayingNote = changedNote; | |||
| voice->noteTimbreChanged(); | |||
| } | |||
| } | |||
| } | |||
| void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isCurrentlyPlayingNote (changedNote)) | |||
| { | |||
| voice->currentlyPlayingNote = changedNote; | |||
| voice->noteKeyStateChanged(); | |||
| } | |||
| } | |||
| } | |||
| void MPESynthesiser::noteReleased (MPENote finishedNote) | |||
| { | |||
| const ScopedLock sl (voicesLock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| MPESynthesiserVoice* const 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 (int 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 (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (! voice->isActive()) | |||
| return voice; | |||
| } | |||
| if (stealIfNoneAvailable) | |||
| return findVoiceToSteal (noteToFindVoiceFor); | |||
| return nullptr; | |||
| } | |||
| struct MPEVoiceAgeSorter | |||
| { | |||
| static int compareElements (MPESynthesiserVoice* v1, MPESynthesiserVoice* v2) noexcept | |||
| { | |||
| return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0); | |||
| } | |||
| }; | |||
| 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 (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| jassert (voice->isActive()); // We wouldn't be here otherwise | |||
| MPEVoiceAgeSorter sorter; | |||
| usableVoices.addSorted (sorter, voice); | |||
| if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
| { | |||
| const int 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; | |||
| const int numUsableVoices = usableVoices.size(); | |||
| // 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 (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| 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 (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
| return voice; | |||
| } | |||
| // Oldest voice that doesn't have a finger on it: | |||
| for (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != low && voice != top | |||
| && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown | |||
| && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) | |||
| return voice; | |||
| } | |||
| // Oldest voice that isn't protected | |||
| for (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| 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 (MPESynthesiserVoice* voice = findFreeVoice (MPENote(), true)) | |||
| voices.removeObject (voice); | |||
| else | |||
| voices.remove (0); // if there's no voice to steal, kill the oldest voice | |||
| } | |||
| } | |||
| void MPESynthesiser::turnOffAllVoices (bool allowTailOff) | |||
| { | |||
| // first turn off all voices (it's more efficient to do this immediately | |||
| // rather than to go through the MPEInstrument for this). | |||
| for (int i = voices.size(); --i >= 0;) | |||
| voices.getUnchecked (i)->noteStopped (allowTailOff); | |||
| // finally make sure the MPE Instrument also doesn't have any notes anymore. | |||
| instrument->releaseAllNotes(); | |||
| } | |||
| //============================================================================== | |||
| void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isActive()) | |||
| voice->renderNextBlock (buffer, startSample, numSamples); | |||
| } | |||
| } | |||
| void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
| if (voice->isActive()) | |||
| voice->renderNextBlock (buffer, startSample, numSamples); | |||
| } | |||
| } | |||
| @@ -0,0 +1,313 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPESynthesiser_H_INCLUDED | |||
| #define JUCE_MPESynthesiser_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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, MPESythesiserVoice, MPENote, MPEInstrument | |||
| */ | |||
| class JUCE_API MPESynthesiser : public MPESynthesiserBase | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| /** Constructor. | |||
| You'll need to add some voices before it'll make any sound. | |||
| @see addVoice | |||
| */ | |||
| MPESynthesiser(); | |||
| /** Constructor to pass to the synthesiser a custom MPEInstrument object | |||
| to handle the MPE note state, MIDI channel assignment etc. | |||
| (in case you need custom logic for this that goes beyond MIDI and MPE). | |||
| The synthesiser will take ownership of this object. | |||
| @see MPESynthesiserBase, MPEInstrument | |||
| */ | |||
| MPESynthesiser (MPEInstrument* instrument); | |||
| /** Destructor. */ | |||
| ~MPESynthesiser(); | |||
| //========================================================================== | |||
| /** Deletes all voices. */ | |||
| void clearVoices(); | |||
| /** Returns the number of voices that have been added. */ | |||
| int getNumVoices() const noexcept { return voices.size(); } | |||
| /** Returns one of the voices that have been added. */ | |||
| MPESynthesiserVoice* getVoice (int index) const; | |||
| /** Adds a new voice to the synth. | |||
| All the voices should be the same class of object and are treated equally. | |||
| The object passed in will be managed by the synthesiser, which will delete | |||
| it later on when no longer needed. The caller should not retain a pointer to the | |||
| voice. | |||
| */ | |||
| void addVoice (MPESynthesiserVoice* newVoice); | |||
| /** Deletes one of the voices. */ | |||
| void removeVoice (int index); | |||
| /** Reduces the number of voices to newNumVoices. | |||
| This will repeatedly call findVoiceToSteal() and remove that voice, until | |||
| the total number of voices equals newNumVoices. If newNumVoices is greater than | |||
| or equal to the current number of voices, this method does nothing. | |||
| */ | |||
| void reduceNumVoices (int newNumVoices); | |||
| /** Release all MPE notes and turn off all voices. | |||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||
| This method is meant to be called by the user, for example to implement | |||
| a MIDI panic button in a synth. | |||
| */ | |||
| virtual void turnOffAllVoices (bool allowTailOff); | |||
| //========================================================================== | |||
| /** If set to true, then the synth will try to take over an existing voice if | |||
| it runs out and needs to play another note. | |||
| The value of this boolean is passed into findFreeVoice(), so the result will | |||
| depend on the implementation of this method. | |||
| */ | |||
| void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } | |||
| /** Returns true if note-stealing is enabled. */ | |||
| bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } | |||
| //========================================================================== | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||
| This overrides the implementation in MPESynthesiserBase, to additionally | |||
| propagate the new value to the voices so that they can use it to render the correct | |||
| pitches. | |||
| */ | |||
| void setCurrentPlaybackSampleRate (double newRate) override; | |||
| //========================================================================== | |||
| /** Handle incoming MIDI events. | |||
| This method will be called automatically according to the MIDI data passed | |||
| into renderNextBlock(), but you can also call it yourself to manually | |||
| inject MIDI events. | |||
| This implementation forwards program change messages and non-MPE-related | |||
| controller messages to handleProgramChange and handleController, respectively, | |||
| and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal | |||
| with MPE-related MIDI messages used for MPE notes, zones etc. | |||
| This method can be overridden further if you need to do custom MIDI | |||
| handling on top of what is provided here. | |||
| */ | |||
| void handleMidiEvent (const MidiMessage&) override; | |||
| /** Callback for MIDI controller messages. The default implementation | |||
| provided here does nothing; override this method if you need custom | |||
| MIDI controller handling on top of MPE. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). | |||
| */ | |||
| virtual void handleController (int /*midiChannel*/, | |||
| int /*controllerNumber*/, | |||
| int /*controllerValue*/) {} | |||
| /** Callback for MIDI program change messages. The default implementation | |||
| provided here does nothing; override this method if you need to handle | |||
| those messages. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). | |||
| */ | |||
| virtual void handleProgramChange (int /*midiChannel*/, | |||
| int /*programNumber*/) {} | |||
| protected: | |||
| //============================================================================== | |||
| /** Attempts to start playing a new note. | |||
| The default method here will find a free voice that is appropriate for | |||
| playing the given MPENote, and use that voice to start playing the sound. | |||
| If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), | |||
| the synthesiser will use the voice stealing algorithm to find a free voice for | |||
| the note (if no voices are free otherwise). | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
| will become inconsistent. | |||
| */ | |||
| virtual void noteAdded (MPENote newNote) override; | |||
| /** Stops playing a note. | |||
| This will be called whenever an MPE note is released (either by a note-off message, | |||
| or by a sustain/sostenuto pedal release for a note that already received a note-off), | |||
| and should therefore stop playing. | |||
| This will find any voice that is currently playing finishedNote, | |||
| turn its currently playing note off, and call its noteStopped callback. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
| will become inconsistent. | |||
| */ | |||
| virtual void noteReleased (MPENote finishedNote) override; | |||
| /** Will find any voice that is currently playing changedNote, update its | |||
| currently playing note, and call its notePressureChanged method. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself. | |||
| */ | |||
| virtual void notePressureChanged (MPENote changedNote) override; | |||
| /** Will find any voice that is currently playing changedNote, update its | |||
| currently playing note, and call its notePitchbendChanged method. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself. | |||
| */ | |||
| virtual void notePitchbendChanged (MPENote changedNote) override; | |||
| /** Will find any voice that is currently playing changedNote, update its | |||
| currently playing note, and call its noteTimbreChanged method. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself. | |||
| */ | |||
| virtual void noteTimbreChanged (MPENote changedNote) override; | |||
| /** Will find any voice that is currently playing changedNote, update its | |||
| currently playing note, and call its noteKeyStateChanged method. | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(). Do not call it yourself. | |||
| */ | |||
| virtual void noteKeyStateChanged (MPENote changedNote) override; | |||
| //========================================================================== | |||
| /** This will simply call renderNextBlock for each currently active | |||
| voice and fill the buffer with the sum. | |||
| Override this method if you need to do more work to render your audio. | |||
| */ | |||
| virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||
| int startSample, | |||
| int numSamples) override; | |||
| /** This will simply call renderNextBlock for each currently active | |||
| voice and fill the buffer with the sum. (souble-precision version) | |||
| Override this method if you need to do more work to render your audio. | |||
| */ | |||
| virtual void renderNextSubBlock (AudioBuffer<double>& outputAudio, | |||
| int startSample, | |||
| int numSamples) override; | |||
| //========================================================================== | |||
| /** Searches through the voices to find one that's not currently playing, and | |||
| which can play the given MPE note. | |||
| If all voices are active and stealIfNoneAvailable is false, this returns | |||
| a nullptr. If all voices are active and stealIfNoneAvailable is true, | |||
| this will call findVoiceToSteal() to find a voice. | |||
| If you need to find a free voice for something else than playing a note | |||
| (e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. | |||
| */ | |||
| virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, | |||
| bool stealIfNoneAvailable) const; | |||
| /** Chooses a voice that is most suitable for being re-used to play a new | |||
| note, or for being deleted by reduceNumVoices. | |||
| The default method will attempt to find the oldest voice that isn't the | |||
| bottom or top note being played. If that's not suitable for your synth, | |||
| you can override this method and do something more cunning instead. | |||
| If you pass a valid MPENote for the optional argument, then the note number | |||
| of that note will be taken into account for finding the ideal voice to steal. | |||
| If you pass an invalid (default-constructed) MPENote instead, this part of | |||
| the algorithm will be ignored. | |||
| */ | |||
| virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; | |||
| /** Starts a specified voice and tells it to play a particular MPENote. | |||
| You should never need to call this, it's called internally by | |||
| MPESynthesiserBase::instrument via the noteStarted callback, | |||
| but is protected in case it's useful for some custom subclasses. | |||
| */ | |||
| void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); | |||
| /** Stops a given voice and tells it to stop playing a particular MPENote | |||
| (which should be the same note it is actually playing). | |||
| You should never need to call this, it's called internally by | |||
| MPESynthesiserBase::instrument via the noteReleased callback, | |||
| but is protected in case it's useful for some custom subclasses. | |||
| */ | |||
| void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); | |||
| //========================================================================== | |||
| OwnedArray<MPESynthesiserVoice> voices; | |||
| private: | |||
| //========================================================================== | |||
| bool shouldStealVoices; | |||
| CriticalSection voicesLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||
| }; | |||
| #endif // JUCE_MPESynthesiser_H_INCLUDED | |||
| @@ -0,0 +1,161 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MPESynthesiserBase::MPESynthesiserBase() | |||
| : instrument (new MPEInstrument), | |||
| sampleRate (0), | |||
| minimumSubBlockSize (32) | |||
| { | |||
| instrument->addListener (this); | |||
| } | |||
| MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst) | |||
| : instrument (inst), | |||
| sampleRate (0), | |||
| minimumSubBlockSize (32) | |||
| { | |||
| 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::handleMidiEvent (const MidiMessage& m) | |||
| { | |||
| instrument->processNextMidiEvent (m); | |||
| } | |||
| //============================================================================== | |||
| template <typename floatType> | |||
| void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| const MidiBuffer& inputMidi, | |||
| int startSample, | |||
| int numSamples) | |||
| { | |||
| // you must set the sample rate before using this! | |||
| jassert (sampleRate != 0); | |||
| MidiBuffer::Iterator midiIterator (inputMidi); | |||
| midiIterator.setNextSamplePosition (startSample); | |||
| int midiEventPos; | |||
| MidiMessage m; | |||
| const ScopedLock sl (renderAudioLock); | |||
| while (numSamples > 0) | |||
| { | |||
| if (! midiIterator.getNextEvent (m, midiEventPos)) | |||
| { | |||
| renderNextSubBlock (outputAudio, startSample, numSamples); | |||
| return; | |||
| } | |||
| const int samplesToNextMidiMessage = midiEventPos - startSample; | |||
| if (samplesToNextMidiMessage >= numSamples) | |||
| { | |||
| renderNextSubBlock (outputAudio, startSample, numSamples); | |||
| handleMidiEvent (m); | |||
| break; | |||
| } | |||
| if (samplesToNextMidiMessage < minimumSubBlockSize) | |||
| { | |||
| handleMidiEvent (m); | |||
| continue; | |||
| } | |||
| renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | |||
| handleMidiEvent (m); | |||
| startSample += samplesToNextMidiMessage; | |||
| numSamples -= samplesToNextMidiMessage; | |||
| } | |||
| while (midiIterator.getNextEvent (m, midiEventPos)) | |||
| handleMidiEvent (m); | |||
| } | |||
| // explicit instantiation for supported float types: | |||
| template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int); | |||
| template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int); | |||
| //============================================================================== | |||
| void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
| { | |||
| if (sampleRate != newRate) | |||
| { | |||
| const ScopedLock sl (renderAudioLock); | |||
| instrument->releaseAllNotes(); | |||
| sampleRate = newRate; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||
| { | |||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||
| minimumSubBlockSize = numSamples; | |||
| } | |||
| @@ -0,0 +1,194 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPESynthesiserBase_H_INCLUDED | |||
| #define JUCE_MPESynthesiserBase_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| */ | |||
| struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| /** Constructor. */ | |||
| MPESynthesiserBase(); | |||
| /** Constructor. | |||
| If you use this constructor, the synthesiser will take ownership of the | |||
| provided instrument object, and will use it internally to handle the | |||
| MPE note state logic. | |||
| This is useful if you want to use an instance of your own class derived | |||
| from MPEInstrument for the MPE logic. | |||
| */ | |||
| MPESynthesiserBase (MPEInstrument* instrument); | |||
| //========================================================================== | |||
| /** Returns the synthesiser's internal MPE zone layout. | |||
| This happens by value, to enforce thread-safety and class invariants. | |||
| */ | |||
| MPEZoneLayout getZoneLayout() const noexcept; | |||
| /** Re-sets the synthesiser's internal MPE zone layout to the one passed in. | |||
| As a side effect, this will discard all currently playing notes, | |||
| call noteReleased for all of them, and disable legacy mode (if previously enabled). | |||
| */ | |||
| void setZoneLayout (MPEZoneLayout newLayout); | |||
| //========================================================================== | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being | |||
| used to render. | |||
| */ | |||
| virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return sampleRate; } | |||
| //========================================================================== | |||
| /** Creates the next block of audio output. | |||
| Call this to make sound. This will chop up the AudioBuffer into subBlock | |||
| pieces separated by events in the MIDI buffer, and then call | |||
| processNextSubBlock on each one of them. In between you will get calls | |||
| to noteAdded/Changed/Finished, where you can update parameters that | |||
| depend on those notes to use for your audio rendering. | |||
| */ | |||
| template <typename floatType> | |||
| void renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| const MidiBuffer& inputMidi, | |||
| int startSample, | |||
| int numSamples); | |||
| //========================================================================== | |||
| /** Handle incoming MIDI events (called from renderNextBlock). | |||
| The default implementation provided here simply forwards everything | |||
| to MPEInstrument::processNextMidiEvent, where it is used to update the | |||
| MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. | |||
| This method can be overridden if you need to do custom MIDI handling | |||
| on top of MPE. The MPESynthesiser class overrides this to implement | |||
| callbacks for MIDI program changes and non-MPE-related MIDI controller | |||
| messages. | |||
| */ | |||
| virtual void handleMidiEvent (const MidiMessage&); | |||
| //========================================================================== | |||
| /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||
| When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||
| into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||
| sub-blocks that are rendered with multiple calls to renderVoices(). | |||
| Obviously in a pathological case where there are midi messages on every sample, then | |||
| renderVoices() could be called once per sample and lead to poor performance, so this | |||
| setting allows you to set a lower limit on the block size. | |||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
| accuracy, which is probably fine for most purposes, but you may want to increase or | |||
| decrease this value for your synth. | |||
| */ | |||
| void setMinimumRenderingSubdivisionSize (int numSamples) 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); | |||
| 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 */ | |||
| ScopedPointer<MPEInstrument> instrument; | |||
| /** @internal */ | |||
| CriticalSection renderAudioLock; | |||
| private: | |||
| //========================================================================== | |||
| double sampleRate; | |||
| int minimumSubBlockSize; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||
| }; | |||
| #endif // JUCE_MPESynthesiserBase_H_INCLUDED | |||
| @@ -0,0 +1,53 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MPESynthesiserVoice::MPESynthesiserVoice() | |||
| : currentSampleRate (0), noteStartTime (0) | |||
| { | |||
| } | |||
| MPESynthesiserVoice::~MPESynthesiserVoice() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept | |||
| { | |||
| return isActive() && currentlyPlayingNote.noteID == note.noteID; | |||
| } | |||
| bool MPESynthesiserVoice::isPlayingButReleased() const noexcept | |||
| { | |||
| return isActive() && currentlyPlayingNote.keyState == MPENote::off; | |||
| } | |||
| bool MPESynthesiserVoice::wasStartedBefore (const MPESynthesiserVoice& other) const noexcept | |||
| { | |||
| return noteStartTime < other.noteStartTime; | |||
| } | |||
| void MPESynthesiserVoice::clearCurrentNote() noexcept | |||
| { | |||
| currentlyPlayingNote = MPENote(); | |||
| } | |||
| @@ -0,0 +1,191 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEVoice_H_INCLUDED | |||
| #define JUCE_MPEVoice_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| */ | |||
| class JUCE_API MPESynthesiserVoice | |||
| { | |||
| public: | |||
| //======================================================================== | |||
| /** Constructor. */ | |||
| MPESynthesiserVoice(); | |||
| /** Destructor. */ | |||
| virtual ~MPESynthesiserVoice(); | |||
| /** Returns the MPENote that this voice is currently playing. | |||
| Returns an invalid MPENote if no note is playing | |||
| (you can check this using MPENote::isValid() or MPEVoice::isActive()). | |||
| */ | |||
| MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } | |||
| /** Returns true if the voice is currently playing the given MPENote | |||
| (as identified by the note's initial note number and MIDI channel). | |||
| */ | |||
| bool isCurrentlyPlayingNote (MPENote note) const noexcept; | |||
| /** Returns true if this voice is currently busy playing a sound. | |||
| By default this just checks whether getCurrentlyPlayingNote() | |||
| returns a valid MPE note, but can be overridden for more advanced checking. | |||
| */ | |||
| virtual bool isActive() const { return currentlyPlayingNote.isValid(); } | |||
| /** Returns true if a voice is sounding in its release phase. **/ | |||
| bool isPlayingButReleased() const noexcept; | |||
| /** Called by the MPESynthesiser to let the voice know that a new note has started on it. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| */ | |||
| virtual void noteStarted() = 0; | |||
| /** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||
| sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||
| and allow the synth to reassign it another sound. | |||
| If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to | |||
| begin fading out its sound, and it can stop playing until it's finished. As soon as it | |||
| finishes playing (during the rendering callback), it must make sure that it calls | |||
| clearCurrentNote(). | |||
| */ | |||
| virtual void noteStopped (bool allowTailOff) = 0; | |||
| /** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
| has changed its pressure value. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| */ | |||
| virtual void notePressureChanged() = 0; | |||
| /** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
| has changed its pitchbend value. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency | |||
| of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend. | |||
| */ | |||
| virtual void notePitchbendChanged() = 0; | |||
| /** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
| has changed its timbre value. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| */ | |||
| virtual void noteTimbreChanged() = 0; | |||
| /** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
| has changed its key state. | |||
| This typically happens when a sustain or sostenuto pedal is pressed or released (on | |||
| an MPE channel relevant for this note), or if the note key is lifted while the sustained | |||
| or sostenuto pedal is still held down. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| */ | |||
| virtual void noteKeyStateChanged() = 0; | |||
| /** Renders the next block of data for this voice. | |||
| The output audio data must be added to the current contents of the buffer provided. | |||
| Only the region of the buffer between startSample and (startSample + numSamples) | |||
| should be altered by this method. | |||
| If the voice is currently silent, it should just return without doing anything. | |||
| If the sound that the voice is playing finishes during the course of this rendered | |||
| block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. | |||
| The size of the blocks that are rendered can change each time it is called, and may | |||
| involve rendering as little as 1 sample at a time. In between rendering callbacks, | |||
| the voice's methods will be called to tell it about note and controller events. | |||
| */ | |||
| virtual void renderNextBlock (AudioBuffer<float>& outputBuffer, | |||
| int startSample, | |||
| int numSamples) = 0; | |||
| /** Renders the next block of 64-bit data for this voice. | |||
| Support for 64-bit audio is optional. You can choose to not override this method if | |||
| you don't need it (the default implementation simply does nothing). | |||
| */ | |||
| virtual void renderNextBlock (AudioBuffer<double>& /*outputBuffer*/, | |||
| int /*startSample*/, | |||
| int /*numSamples*/) {} | |||
| /** Changes the voice's reference sample rate. | |||
| The rate is set so that subclasses know the output rate and can set their pitch | |||
| accordingly. | |||
| This method is called by the synth, and subclasses can access the current rate with | |||
| the currentSampleRate member. | |||
| */ | |||
| virtual void setCurrentSampleRate (double newRate) { currentSampleRate = newRate; } | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return currentSampleRate; } | |||
| /** Returns true if this voice started playing its current note before the other voice did. */ | |||
| bool wasStartedBefore (const MPESynthesiserVoice& other) const noexcept; | |||
| protected: | |||
| //========================================================================== | |||
| /** Resets the state of this voice after a sound has finished playing. | |||
| The subclass must call this when it finishes playing a note and becomes available | |||
| to play new ones. | |||
| It must either call it in the stopNote() method, or if the voice is tailing off, | |||
| then it should call it later during the renderNextBlock method, as soon as it | |||
| finishes its tail-off. | |||
| It can also be called at any time during the render callback if the sound happens | |||
| to have finished, e.g. if it's playing a sample and the sample finishes. | |||
| */ | |||
| void clearCurrentNote() noexcept; | |||
| //========================================================================== | |||
| double currentSampleRate; | |||
| MPENote currentlyPlayingNote; | |||
| private: | |||
| //========================================================================== | |||
| friend class MPESynthesiser; | |||
| uint32 noteStartTime; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) | |||
| }; | |||
| #endif // JUCE_MPEVoice_H_INCLUDED | |||
| @@ -0,0 +1,170 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MPEValue::MPEValue() noexcept : normalisedValue (8192) | |||
| { | |||
| } | |||
| MPEValue::MPEValue (int value) : normalisedValue (value) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| MPEValue MPEValue::from7BitInt (int value) noexcept | |||
| { | |||
| jassert (value >= 0 && value <= 127); | |||
| const int valueAs14Bit = value <= 64 ? value << 7 : int (jmap<float> (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192; | |||
| return MPEValue (valueAs14Bit); | |||
| } | |||
| MPEValue MPEValue::from14BitInt (int value) noexcept | |||
| { | |||
| jassert (value >= 0 && value <= 16383); | |||
| return MPEValue (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") {} | |||
| 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::fabs (expectedValue)) * maxRelativeError; | |||
| expect (std::fabs (expectedValue - actualValue) < maxAbsoluteError); | |||
| } | |||
| }; | |||
| static MPEValueTests MPEValueUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,96 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEVALUE_H_INCLUDED | |||
| #define JUCE_MPEVALUE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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. | |||
| */ | |||
| 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; | |||
| }; | |||
| #endif // JUCE_MPEVALUE_H_INCLUDED | |||
| @@ -0,0 +1,302 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| namespace | |||
| { | |||
| void checkAndLimitZoneParameters (int minValue, | |||
| int maxValue, | |||
| int& valueToCheckAndLimit) noexcept | |||
| { | |||
| if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) | |||
| { | |||
| // if you hit this, one of the parameters you supplied for MPEZone | |||
| // 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); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| MPEZone::MPEZone (int masterChannel_, | |||
| int numNoteChannels_, | |||
| int perNotePitchbendRange_, | |||
| int masterPitchbendRange_) noexcept | |||
| : masterChannel (masterChannel_), | |||
| numNoteChannels (numNoteChannels_), | |||
| perNotePitchbendRange (perNotePitchbendRange_), | |||
| masterPitchbendRange (masterPitchbendRange_) | |||
| { | |||
| checkAndLimitZoneParameters (1, 15, masterChannel); | |||
| checkAndLimitZoneParameters (1, 16 - masterChannel, numNoteChannels); | |||
| checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); | |||
| checkAndLimitZoneParameters (0, 96, masterPitchbendRange); | |||
| } | |||
| //============================================================================== | |||
| int MPEZone::getMasterChannel() const noexcept | |||
| { | |||
| return masterChannel; | |||
| } | |||
| int MPEZone::getNumNoteChannels() const noexcept | |||
| { | |||
| return numNoteChannels; | |||
| } | |||
| int MPEZone::getFirstNoteChannel() const noexcept | |||
| { | |||
| return masterChannel + 1; | |||
| } | |||
| int MPEZone::getLastNoteChannel() const noexcept | |||
| { | |||
| return masterChannel + numNoteChannels; | |||
| } | |||
| Range<int> MPEZone::getNoteChannelRange() const noexcept | |||
| { | |||
| return Range<int>::withStartAndLength (getFirstNoteChannel(), getNumNoteChannels()); | |||
| } | |||
| bool MPEZone::isUsingChannel (int channel) const noexcept | |||
| { | |||
| jassert (channel > 0 && channel <= 16); | |||
| return channel >= masterChannel && channel <= masterChannel + numNoteChannels; | |||
| } | |||
| bool MPEZone::isUsingChannelAsNoteChannel (int channel) const noexcept | |||
| { | |||
| jassert (channel > 0 && channel <= 16); | |||
| return channel > masterChannel && channel <= masterChannel + numNoteChannels; | |||
| } | |||
| int MPEZone::getPerNotePitchbendRange() const noexcept | |||
| { | |||
| return perNotePitchbendRange; | |||
| } | |||
| int MPEZone::getMasterPitchbendRange() const noexcept | |||
| { | |||
| return masterPitchbendRange; | |||
| } | |||
| void MPEZone::setPerNotePitchbendRange (int rangeInSemitones) noexcept | |||
| { | |||
| checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||
| perNotePitchbendRange = rangeInSemitones; | |||
| } | |||
| void MPEZone::setMasterPitchbendRange (int rangeInSemitones) noexcept | |||
| { | |||
| checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||
| masterPitchbendRange = rangeInSemitones; | |||
| } | |||
| //============================================================================== | |||
| bool MPEZone::overlapsWith (MPEZone other) const noexcept | |||
| { | |||
| if (masterChannel == other.masterChannel) | |||
| return true; | |||
| if (masterChannel > other.masterChannel) | |||
| return other.overlapsWith (*this); | |||
| return masterChannel + numNoteChannels >= other.masterChannel; | |||
| } | |||
| //============================================================================== | |||
| bool MPEZone::truncateToFit (MPEZone other) noexcept | |||
| { | |||
| const int masterChannelDiff = other.masterChannel - masterChannel; | |||
| // we need at least 2 channels to be left after truncation: | |||
| // 1 master channel and 1 note channel. otherwise we can't truncate. | |||
| if (masterChannelDiff < 2) | |||
| return false; | |||
| numNoteChannels = jmin (numNoteChannels, masterChannelDiff - 1); | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class MPEZoneTests : public UnitTest | |||
| { | |||
| public: | |||
| MPEZoneTests() : UnitTest ("MPEZone class") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("initialisation"); | |||
| { | |||
| { | |||
| MPEZone zone (1, 10); | |||
| expectEquals (zone.getMasterChannel(), 1); | |||
| expectEquals (zone.getNumNoteChannels(), 10); | |||
| expectEquals (zone.getFirstNoteChannel(), 2); | |||
| expectEquals (zone.getLastNoteChannel(), 11); | |||
| expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
| expectEquals (zone.getMasterPitchbendRange(), 2); | |||
| expect (zone.isUsingChannel (1)); | |||
| expect (zone.isUsingChannel (2)); | |||
| expect (zone.isUsingChannel (10)); | |||
| expect (zone.isUsingChannel (11)); | |||
| expect (! zone.isUsingChannel (12)); | |||
| expect (! zone.isUsingChannel (16)); | |||
| expect (! zone.isUsingChannelAsNoteChannel (1)); | |||
| expect (zone.isUsingChannelAsNoteChannel (2)); | |||
| expect (zone.isUsingChannelAsNoteChannel (10)); | |||
| expect (zone.isUsingChannelAsNoteChannel (11)); | |||
| expect (! zone.isUsingChannelAsNoteChannel (12)); | |||
| expect (! zone.isUsingChannelAsNoteChannel (16)); | |||
| } | |||
| { | |||
| MPEZone zone (5, 4); | |||
| expectEquals (zone.getMasterChannel(), 5); | |||
| expectEquals (zone.getNumNoteChannels(), 4); | |||
| expectEquals (zone.getFirstNoteChannel(), 6); | |||
| expectEquals (zone.getLastNoteChannel(), 9); | |||
| expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
| expectEquals (zone.getMasterPitchbendRange(), 2); | |||
| expect (! zone.isUsingChannel (1)); | |||
| expect (! zone.isUsingChannel (4)); | |||
| expect (zone.isUsingChannel (5)); | |||
| expect (zone.isUsingChannel (6)); | |||
| expect (zone.isUsingChannel (8)); | |||
| expect (zone.isUsingChannel (9)); | |||
| expect (! zone.isUsingChannel (10)); | |||
| expect (! zone.isUsingChannel (16)); | |||
| expect (! zone.isUsingChannelAsNoteChannel (5)); | |||
| expect (zone.isUsingChannelAsNoteChannel (6)); | |||
| expect (zone.isUsingChannelAsNoteChannel (8)); | |||
| expect (zone.isUsingChannelAsNoteChannel (9)); | |||
| expect (! zone.isUsingChannelAsNoteChannel (10)); | |||
| } | |||
| } | |||
| beginTest ("getNoteChannelRange"); | |||
| { | |||
| MPEZone zone (2, 10); | |||
| Range<int> noteChannelRange = zone.getNoteChannelRange(); | |||
| expectEquals (noteChannelRange.getStart(), 3); | |||
| expectEquals (noteChannelRange.getEnd(), 13); | |||
| } | |||
| beginTest ("setting master pitchbend range"); | |||
| { | |||
| MPEZone zone (1, 10); | |||
| zone.setMasterPitchbendRange (96); | |||
| expectEquals (zone.getMasterPitchbendRange(), 96); | |||
| zone.setMasterPitchbendRange (0); | |||
| expectEquals (zone.getMasterPitchbendRange(), 0); | |||
| expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
| } | |||
| beginTest ("setting per-note pitchbend range"); | |||
| { | |||
| MPEZone zone (1, 10); | |||
| zone.setPerNotePitchbendRange (96); | |||
| expectEquals (zone.getPerNotePitchbendRange(), 96); | |||
| zone.setPerNotePitchbendRange (0); | |||
| expectEquals (zone.getPerNotePitchbendRange(), 0); | |||
| expectEquals (zone.getMasterPitchbendRange(), 2); | |||
| } | |||
| beginTest ("checking overlap"); | |||
| { | |||
| testOverlapsWith (1, 10, 1, 10, true); | |||
| testOverlapsWith (1, 4, 6, 3, false); | |||
| testOverlapsWith (1, 4, 8, 3, false); | |||
| testOverlapsWith (2, 10, 2, 8, true); | |||
| testOverlapsWith (1, 10, 3, 2, true); | |||
| testOverlapsWith (3, 10, 5, 9, true); | |||
| } | |||
| beginTest ("truncating"); | |||
| { | |||
| testTruncateToFit (1, 10, 3, 10, true, 1, 1); | |||
| testTruncateToFit (3, 10, 1, 10, false, 3, 10); | |||
| testTruncateToFit (1, 10, 5, 8, true, 1, 3); | |||
| testTruncateToFit (5, 8, 1, 10, false, 5, 8); | |||
| testTruncateToFit (1, 10, 4, 3, true, 1, 2); | |||
| testTruncateToFit (4, 3, 1, 10, false, 4, 3); | |||
| testTruncateToFit (1, 3, 5, 3, true, 1, 3); | |||
| testTruncateToFit (5, 3, 1, 3, false, 5, 3); | |||
| testTruncateToFit (1, 3, 7, 3, true, 1, 3); | |||
| testTruncateToFit (7, 3, 1, 3, false, 7, 3); | |||
| testTruncateToFit (1, 10, 2, 10, false, 1, 10); | |||
| testTruncateToFit (2, 10, 1, 10, false, 2, 10); | |||
| } | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst, | |||
| int masterChannelSecond, int numNoteChannelsSecond, | |||
| bool expectedRetVal) | |||
| { | |||
| MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||
| MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||
| expect (first.overlapsWith (second) == expectedRetVal); | |||
| expect (second.overlapsWith (first) == expectedRetVal); | |||
| } | |||
| //========================================================================== | |||
| void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst, | |||
| int masterChannelSecond, int numNoteChannelsSecond, | |||
| bool expectedRetVal, | |||
| int masterChannelFirstAfter, int numNoteChannelsFirstAfter) | |||
| { | |||
| MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||
| MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||
| expect (first.truncateToFit (second) == expectedRetVal); | |||
| expectEquals (first.getMasterChannel(), masterChannelFirstAfter); | |||
| expectEquals (first.getNumNoteChannels(), numNoteChannelsFirstAfter); | |||
| } | |||
| }; | |||
| static MPEZoneTests MPEZoneUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,132 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEZONE_H_INCLUDED | |||
| #define JUCE_MPEZONE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| This struct represents an MPE Zone. | |||
| An MPE Zone occupies one master MIDI channel and an arbitrary | |||
| number of note channels that immediately follow the master channel. | |||
| It also defines a pitchbend range (in semitones) to be applied for per-note | |||
| pitchbends and master pitchbends, respectively. | |||
| @see MPEZoneLayout | |||
| */ | |||
| struct JUCE_API MPEZone | |||
| { | |||
| /** Constructor. | |||
| Creates an MPE zone with the given master channel and | |||
| number of note channels. | |||
| @param masterChannel The master MIDI channel of the new zone. | |||
| All master (not per-note) messages should be send to this channel. | |||
| Must be between 1 and 15. Otherwise, the behaviour | |||
| is undefined. | |||
| @param numNoteChannels The number of note channels that the new zone | |||
| should use. The first note channel will be one higher | |||
| than the master channel. The number of note channels | |||
| must be at least 1 and no greater than 16 - masterChannel. | |||
| Otherwise, the behaviour is undefined. | |||
| @param perNotePitchbendRange The per-note pitchbend range in semitones of the new zone. | |||
| Must be between 0 and 96. Otherwise the behaviour is undefined. | |||
| If unspecified, the default setting of +/- 48 semitones | |||
| will be used. | |||
| @param masterPitchbendRange The master pitchbend range in semitones of the new zone. | |||
| Must be between 0 and 96. Otherwise the behaviour is undefined. | |||
| If unspecified, the default setting of +/- 2 semitones | |||
| will be used. | |||
| */ | |||
| MPEZone (int masterChannel, | |||
| int numNoteChannels, | |||
| int perNotePitchbendRange = 48, | |||
| int masterPitchbendRange = 2) noexcept; | |||
| /* Returns the MIDI master channel of this zone. */ | |||
| int getMasterChannel() const noexcept; | |||
| /** Returns the number of note channels occupied by this zone. */ | |||
| int getNumNoteChannels() const noexcept; | |||
| /* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ | |||
| int getFirstNoteChannel() const noexcept; | |||
| /* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ | |||
| int getLastNoteChannel() const noexcept; | |||
| /** Returns the MIDI channel numbers of the note channels of this zone as a Range. */ | |||
| Range<int> getNoteChannelRange() const noexcept; | |||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||
| either as a note channel or as the master channel; false otherwise. */ | |||
| bool isUsingChannel (int channel) const noexcept; | |||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||
| as a note channel; false otherwise. */ | |||
| bool isUsingChannelAsNoteChannel (int channel) const noexcept; | |||
| /** Returns the per-note pitchbend range in semitones set for this zone. */ | |||
| int getPerNotePitchbendRange() const noexcept; | |||
| /** Returns the master pitchbend range in semitones set for this zone. */ | |||
| int getMasterPitchbendRange() const noexcept; | |||
| /** Sets the per-note pitchbend range in semitones for this zone. */ | |||
| void setPerNotePitchbendRange (int rangeInSemitones) noexcept; | |||
| /** Sets the master pitchbend range in semitones for this zone. */ | |||
| void setMasterPitchbendRange (int rangeInSemitones) noexcept; | |||
| /** Returns true if the MIDI channels occupied by this zone | |||
| overlap with those occupied by the other zone. | |||
| */ | |||
| bool overlapsWith (MPEZone other) const noexcept; | |||
| /** Tries to truncate this zone in such a way that the range of MIDI channels | |||
| it occupies do not overlap with the other zone, by reducing this zone's | |||
| number of note channels. | |||
| @returns true if the truncation succeeded or if no truncation is necessary | |||
| because the zones do not overlap. False if the zone cannot be truncated | |||
| in a way that would remove the overlap (in this case you need to delete | |||
| the zone to remove the overlap). | |||
| */ | |||
| bool truncateToFit (MPEZone zoneToAvoid) noexcept; | |||
| private: | |||
| //========================================================================== | |||
| int masterChannel; | |||
| int numNoteChannels; | |||
| int perNotePitchbendRange; | |||
| int masterPitchbendRange; | |||
| }; | |||
| #endif // JUCE_MPEZONE_H_INCLUDED | |||
| @@ -0,0 +1,346 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| MPEZoneLayout::MPEZoneLayout() noexcept | |||
| { | |||
| } | |||
| //============================================================================== | |||
| bool MPEZoneLayout::addZone (MPEZone newZone) | |||
| { | |||
| bool noOtherZonesModified = true; | |||
| for (int i = zones.size(); --i >= 0;) | |||
| { | |||
| MPEZone& zone = zones.getReference (i); | |||
| if (zone.overlapsWith (newZone)) | |||
| { | |||
| if (! zone.truncateToFit (newZone)) | |||
| zones.removeRange (i, 1); | |||
| // can't use zones.remove (i) because that requires a default c'tor :-( | |||
| noOtherZonesModified = false; | |||
| } | |||
| } | |||
| zones.add (newZone); | |||
| return noOtherZonesModified; | |||
| } | |||
| //============================================================================== | |||
| int MPEZoneLayout::getNumZones() const noexcept | |||
| { | |||
| return zones.size(); | |||
| } | |||
| MPEZone* MPEZoneLayout::getZoneByIndex (int index) const noexcept | |||
| { | |||
| if (zones.size() < index) | |||
| return nullptr; | |||
| return &(zones.getReference (index)); | |||
| } | |||
| void MPEZoneLayout::clearAllZones() | |||
| { | |||
| zones.clear(); | |||
| } | |||
| //============================================================================== | |||
| 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) | |||
| addZone (MPEZone (rpn.channel - 1, rpn.value)); | |||
| else | |||
| clearAllZones(); | |||
| } | |||
| //============================================================================== | |||
| void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) | |||
| { | |||
| if (MPEZone* zone = getZoneByFirstNoteChannel (rpn.channel)) | |||
| { | |||
| zone->setPerNotePitchbendRange (rpn.value); | |||
| return; | |||
| } | |||
| if (MPEZone* zone = getZoneByMasterChannel (rpn.channel)) | |||
| zone->setMasterPitchbendRange (rpn.value); | |||
| } | |||
| //============================================================================== | |||
| void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) | |||
| { | |||
| MidiBuffer::Iterator iter (buffer); | |||
| MidiMessage message; | |||
| int samplePosition; // not actually used, so no need to initialise. | |||
| while (iter.getNextEvent (message, samplePosition)) | |||
| processNextMidiEvent (message); | |||
| } | |||
| //============================================================================== | |||
| MPEZone* MPEZoneLayout::getZoneByChannel (int channel) const noexcept | |||
| { | |||
| for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||
| if (zone->isUsingChannel (channel)) | |||
| return zone; | |||
| return nullptr; | |||
| } | |||
| MPEZone* MPEZoneLayout::getZoneByMasterChannel (int channel) const noexcept | |||
| { | |||
| for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||
| if (zone->getMasterChannel() == channel) | |||
| return zone; | |||
| return nullptr; | |||
| } | |||
| MPEZone* MPEZoneLayout::getZoneByFirstNoteChannel (int channel) const noexcept | |||
| { | |||
| for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||
| if (zone->getFirstNoteChannel() == channel) | |||
| return zone; | |||
| return nullptr; | |||
| } | |||
| MPEZone* MPEZoneLayout::getZoneByNoteChannel (int channel) const noexcept | |||
| { | |||
| for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||
| if (zone->isUsingChannelAsNoteChannel (channel)) | |||
| return zone; | |||
| return nullptr; | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class MPEZoneLayoutTests : public UnitTest | |||
| { | |||
| public: | |||
| MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("initialisation"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| expectEquals (layout.getNumZones(), 0); | |||
| } | |||
| beginTest ("adding zones"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| expect (layout.addZone (MPEZone (1, 7))); | |||
| expectEquals (layout.getNumZones(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||
| expect (layout.addZone (MPEZone (9, 7))); | |||
| expectEquals (layout.getNumZones(), 2); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||
| expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||
| expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||
| expect (! layout.addZone (MPEZone (5, 3))); | |||
| expectEquals (layout.getNumZones(), 3); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||
| expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||
| expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||
| expectEquals (layout.getZoneByIndex (2)->getMasterChannel(), 5); | |||
| expectEquals (layout.getZoneByIndex (2)->getNumNoteChannels(), 3); | |||
| expect (! layout.addZone (MPEZone (5, 4))); | |||
| expectEquals (layout.getNumZones(), 2); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||
| expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 5); | |||
| expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); | |||
| expect (! layout.addZone (MPEZone (6, 4))); | |||
| expectEquals (layout.getNumZones(), 2); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||
| expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 6); | |||
| expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); | |||
| } | |||
| beginTest ("querying zones"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| layout.addZone (MPEZone (2, 5)); | |||
| layout.addZone (MPEZone (9, 4)); | |||
| expect (layout.getZoneByMasterChannel (1) == nullptr); | |||
| expect (layout.getZoneByMasterChannel (2) != nullptr); | |||
| expect (layout.getZoneByMasterChannel (3) == nullptr); | |||
| expect (layout.getZoneByMasterChannel (8) == nullptr); | |||
| expect (layout.getZoneByMasterChannel (9) != nullptr); | |||
| expect (layout.getZoneByMasterChannel (10) == nullptr); | |||
| expectEquals (layout.getZoneByMasterChannel (2)->getNumNoteChannels(), 5); | |||
| expectEquals (layout.getZoneByMasterChannel (9)->getNumNoteChannels(), 4); | |||
| expect (layout.getZoneByFirstNoteChannel (2) == nullptr); | |||
| expect (layout.getZoneByFirstNoteChannel (3) != nullptr); | |||
| expect (layout.getZoneByFirstNoteChannel (4) == nullptr); | |||
| expect (layout.getZoneByFirstNoteChannel (9) == nullptr); | |||
| expect (layout.getZoneByFirstNoteChannel (10) != nullptr); | |||
| expect (layout.getZoneByFirstNoteChannel (11) == nullptr); | |||
| expectEquals (layout.getZoneByFirstNoteChannel (3)->getNumNoteChannels(), 5); | |||
| expectEquals (layout.getZoneByFirstNoteChannel (10)->getNumNoteChannels(), 4); | |||
| expect (layout.getZoneByNoteChannel (2) == nullptr); | |||
| expect (layout.getZoneByNoteChannel (3) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (4) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (6) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (7) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (8) == nullptr); | |||
| expect (layout.getZoneByNoteChannel (9) == nullptr); | |||
| expect (layout.getZoneByNoteChannel (10) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (11) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (12) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (13) != nullptr); | |||
| expect (layout.getZoneByNoteChannel (14) == nullptr); | |||
| expectEquals (layout.getZoneByNoteChannel (5)->getNumNoteChannels(), 5); | |||
| expectEquals (layout.getZoneByNoteChannel (13)->getNumNoteChannels(), 4); | |||
| } | |||
| beginTest ("clear all zones"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| expect (layout.addZone (MPEZone (1, 7))); | |||
| expect (layout.addZone (MPEZone (10, 2))); | |||
| layout.clearAllZones(); | |||
| expectEquals (layout.getNumZones(), 0); | |||
| } | |||
| beginTest ("process MIDI buffers"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| MidiBuffer buffer; | |||
| buffer = MPEMessages::addZone (MPEZone (1, 7)); | |||
| layout.processNextMidiBuffer (buffer); | |||
| expectEquals (layout.getNumZones(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||
| buffer = MPEMessages::addZone (MPEZone (9, 7)); | |||
| layout.processNextMidiBuffer (buffer); | |||
| expectEquals (layout.getNumZones(), 2); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||
| expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||
| expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||
| MPEZone zone (1, 10); | |||
| buffer = MPEMessages::addZone (zone); | |||
| layout.processNextMidiBuffer (buffer); | |||
| expectEquals (layout.getNumZones(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 10); | |||
| zone.setPerNotePitchbendRange (33); | |||
| zone.setMasterPitchbendRange (44); | |||
| buffer = MPEMessages::masterPitchbendRange (zone); | |||
| buffer.addEvents (MPEMessages::perNotePitchbendRange (zone), 0, -1, 0); | |||
| layout.processNextMidiBuffer (buffer); | |||
| expectEquals (layout.getZoneByIndex (0)->getPerNotePitchbendRange(), 33); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterPitchbendRange(), 44); | |||
| } | |||
| beginTest ("process individual MIDI messages"); | |||
| { | |||
| MPEZoneLayout layout; | |||
| layout.processNextMidiEvent (MidiMessage (0x80, 0x59, 0xd0)); // unrelated note-off msg | |||
| layout.processNextMidiEvent (MidiMessage (0xb1, 0x64, 0x06)); // RPN part 1 | |||
| layout.processNextMidiEvent (MidiMessage (0xb1, 0x65, 0x00)); // RPN part 2 | |||
| layout.processNextMidiEvent (MidiMessage (0xb8, 0x0b, 0x66)); // unrelated CC msg | |||
| layout.processNextMidiEvent (MidiMessage (0xb1, 0x06, 0x03)); // RPN part 3 | |||
| layout.processNextMidiEvent (MidiMessage (0x90, 0x60, 0x00)); // unrelated note-on msg | |||
| expectEquals (layout.getNumZones(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||
| expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||
| } | |||
| } | |||
| }; | |||
| static MPEZoneLayoutTests MPEZoneLayoutUnitTests; | |||
| #endif // JUCE_UNIT_TESTS | |||
| @@ -0,0 +1,129 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MPEZONELAYOUT_H_INCLUDED | |||
| #define JUCE_MPEZONELAYOUT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| This class represents the current MPE zone layout of a device | |||
| capable of handling MPE. | |||
| 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 MPEZone, MPEInstrument | |||
| */ | |||
| class JUCE_API MPEZoneLayout | |||
| { | |||
| public: | |||
| /** Default constructor. | |||
| This will create a layout with no MPE zones. | |||
| You can add an MPE zone using the method addZone. | |||
| */ | |||
| MPEZoneLayout() noexcept; | |||
| /** Adds a new MPE zone to the layout. | |||
| @param newZone The zone to add. | |||
| @return true if the zone was added without modifying any other zones | |||
| added previously to the same zone layout object (if any); | |||
| false if any existing MPE zones had to be truncated | |||
| or deleted entirely in order to to add this new zone. | |||
| (Note: the zone itself will always be added with the channel bounds | |||
| that were specified; this will not fail.) | |||
| */ | |||
| bool addZone (MPEZone newZone); | |||
| /** Removes all currently present MPE zones. */ | |||
| 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); | |||
| /** Returns the current number of MPE zones. */ | |||
| int getNumZones() const noexcept; | |||
| /** Returns a pointer to the MPE zone at the given index, | |||
| or nullptr if there is no such zone. | |||
| */ | |||
| MPEZone* getZoneByIndex (int index) const noexcept; | |||
| /** Returns a pointer to the zone which uses the specified channel (1-16), | |||
| or nullptr if there is no such zone. | |||
| */ | |||
| MPEZone* getZoneByChannel (int midiChannel) const noexcept; | |||
| /** Returns a pointer to the zone which has the specified channel (1-16) | |||
| as its master channel, or nullptr if there is no such zone. | |||
| */ | |||
| MPEZone* getZoneByMasterChannel (int midiChannel) const noexcept; | |||
| /** Returns a pointer to the zone which has the specified channel (1-16) | |||
| as its first note channel, or nullptr if there is no such zone. | |||
| */ | |||
| MPEZone* getZoneByFirstNoteChannel (int midiChannel) const noexcept; | |||
| /** Returns a pointer to the zone which has the specified channel (1-16) | |||
| as one of its note channels, or nullptr if there is no such zone. | |||
| */ | |||
| MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept; | |||
| private: | |||
| //========================================================================== | |||
| Array<MPEZone> zones; | |||
| MidiRPNDetector rpnDetector; | |||
| void processRpnMessage (MidiRPNMessage); | |||
| void processZoneLayoutRpnMessage (MidiRPNMessage); | |||
| void processPitchbendRangeRpnMessage (MidiRPNMessage); | |||
| }; | |||
| #endif // JUCE_MPEZONELAYOUT_H_INCLUDED | |||
| @@ -71,7 +71,7 @@ public: | |||
| virtual bool isLooping() const = 0; | |||
| /** Tells the source whether you'd like it to play in a loop. */ | |||
| virtual void setLooping (bool shouldLoop) { (void) shouldLoop; } | |||
| virtual void setLooping (bool shouldLoop) { ignoreUnused (shouldLoop); } | |||
| }; | |||
| @@ -114,6 +114,7 @@ void Synthesiser::clearVoices() | |||
| SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||
| return voices.add (newVoice); | |||
| } | |||
| @@ -511,13 +512,13 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
| void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
| { | |||
| (void) midiChannel; | |||
| ignoreUnused (midiChannel); | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| } | |||
| void Synthesiser::handleProgramChange (int midiChannel, int programNumber) | |||
| { | |||
| (void) midiChannel; (void) programNumber; | |||
| ignoreUnused (midiChannel, programNumber); | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| } | |||
| @@ -557,6 +558,9 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| // - 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) | |||
| 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 | |||
| @@ -1,30 +0,0 @@ | |||
| /* | |||
| * Carla Juce setup | |||
| * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| #include "juce_events.h" | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_formats.h" | |||
| #if 1 //JUCE_MAC || JUCE_WINDOWS | |||
| # include "juce_audio_devices/AppConfig.h" | |||
| # include "juce_audio_devices/juce_audio_devices.h" | |||
| #endif | |||
| #endif // CARLA_JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| @@ -1,79 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| Build options for juce_audio_devices static library | |||
| ============================================================================== | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_DEVICES_APPCONFIG_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_DEVICES_APPCONFIG_H_INCLUDED | |||
| #include "../juce_events/AppConfig.h" | |||
| #include "../juce_audio_basics/AppConfig.h" | |||
| #include "../juce_audio_formats/AppConfig.h" | |||
| //============================================================================= | |||
| /** 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. | |||
| */ | |||
| #if JUCE_WINDOWS | |||
| #define JUCE_ASIO 1 | |||
| #else | |||
| #define JUCE_ASIO 0 | |||
| #endif | |||
| /** Config: JUCE_WASAPI | |||
| Enables WASAPI audio devices (Windows Vista and above). | |||
| */ | |||
| #define JUCE_WASAPI 0 | |||
| /** Config: JUCE_DIRECTSOUND | |||
| Enables DirectSound audio (MS Windows only). | |||
| */ | |||
| #if JUCE_WINDOWS | |||
| #define JUCE_DIRECTSOUND 1 | |||
| #else | |||
| #define JUCE_DIRECTSOUND 0 | |||
| #endif | |||
| /** Config: JUCE_ALSA | |||
| Enables ALSA audio devices (Linux only). | |||
| */ | |||
| #if 0 //JUCE_LINUX | |||
| #define JUCE_ALSA 1 | |||
| #define JUCE_ALSA_MIDI_INPUT_NAME "Carla" | |||
| #define JUCE_ALSA_MIDI_OUTPUT_NAME "Carla" | |||
| #define JUCE_ALSA_MIDI_INPUT_PORT_NAME "Midi In" | |||
| #define JUCE_ALSA_MIDI_OUTPUT_PORT_NAME "Midi Out" | |||
| #else | |||
| #define JUCE_ALSA 0 | |||
| #endif | |||
| /** Config: JUCE_JACK | |||
| Enables JACK audio devices (Linux only). | |||
| */ | |||
| #if 0 //JUCE_LINUX | |||
| #define JUCE_JACK 1 | |||
| #define JUCE_JACK_CLIENT_NAME "Carla" | |||
| #else | |||
| #define JUCE_JACK 0 | |||
| #endif | |||
| //============================================================================= | |||
| /** 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 | |||
| #endif // CARLA_JUCE_AUDIO_DEVICES_APPCONFIG_H_INCLUDED | |||
| @@ -10,7 +10,7 @@ include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_DEVICES_FLAGS) -w | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_DEVICES_FLAGS) -I.. | |||
| ifeq ($(WIN32),true) | |||
| # BUILD_CXX_FLAGS += -I$(CWD)/includes/asio | |||
| @@ -151,7 +151,7 @@ private: | |||
| File volumeDir; | |||
| Array<File> tracks; | |||
| int currentReaderTrack; | |||
| ScopedPointer <AudioFormatReader> reader; | |||
| ScopedPointer<AudioFormatReader> reader; | |||
| AudioCDReader (const File& volume); | |||
| #elif JUCE_WINDOWS | |||
| @@ -86,6 +86,65 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||
| }; | |||
| //============================================================================== | |||
| // This is an AudioTransportSource which will own it's assigned source | |||
| struct AudioSourceOwningTransportSource : public AudioTransportSource | |||
| { | |||
| AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||
| { | |||
| AudioTransportSource::setSource (s); | |||
| } | |||
| ~AudioSourceOwningTransportSource() | |||
| { | |||
| setSource (nullptr); | |||
| } | |||
| private: | |||
| ScopedPointer<PositionableAudioSource> source; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
| }; | |||
| //============================================================================== | |||
| // An AudioSourcePlayer which will remove itself from the AudioDeviceManager's | |||
| // callback list once it finishes playing its source | |||
| struct AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
| private Timer | |||
| { | |||
| AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) | |||
| : manager (dm), transportSource (ts, ownSource) | |||
| { | |||
| jassert (ts != nullptr); | |||
| manager.addAudioCallback (this); | |||
| AudioSourcePlayer::setSource (transportSource); | |||
| startTimerHz (10); | |||
| } | |||
| ~AutoRemovingSourcePlayer() | |||
| { | |||
| setSource (nullptr); | |||
| manager.removeAudioCallback (this); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) | |||
| delete this; | |||
| } | |||
| void audioDeviceStopped() override | |||
| { | |||
| AudioSourcePlayer::audioDeviceStopped(); | |||
| setSource (nullptr); | |||
| } | |||
| private: | |||
| AudioDeviceManager& manager; | |||
| OptionalScopedPointer<AudioTransportSource> transportSource; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
| }; | |||
| //============================================================================== | |||
| AudioDeviceManager::AudioDeviceManager() | |||
| @@ -103,8 +162,11 @@ AudioDeviceManager::~AudioDeviceManager() | |||
| { | |||
| currentAudioDevice = nullptr; | |||
| defaultMidiOutput = nullptr; | |||
| } | |||
| for (int i = 0; i < callbacks.size(); ++i) | |||
| if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||
| delete p; | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
| @@ -926,143 +988,16 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
| } | |||
| } | |||
| //============================================================================== | |||
| // This is an AudioTransportSource which will own it's assigned source | |||
| class AudioSourceOwningTransportSource : public AudioTransportSource | |||
| { | |||
| public: | |||
| AudioSourceOwningTransportSource() {} | |||
| ~AudioSourceOwningTransportSource() { setSource (nullptr); } | |||
| void setSource (PositionableAudioSource* newSource) | |||
| { | |||
| if (src != newSource) | |||
| { | |||
| ScopedPointer<PositionableAudioSource> oldSourceDeleter (src); | |||
| src = newSource; | |||
| // tell the base class about the new source before deleting the old one | |||
| AudioTransportSource::setSource (newSource); | |||
| } | |||
| } | |||
| private: | |||
| ScopedPointer<PositionableAudioSource> src; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
| }; | |||
| //============================================================================== | |||
| // An Audio player which will remove itself from the AudioDeviceManager's | |||
| // callback list once it finishes playing its source | |||
| class AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
| private ChangeListener | |||
| { | |||
| public: | |||
| struct DeleteOnMessageThread : public CallbackMessage | |||
| { | |||
| DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {} | |||
| void messageCallback() override { delete parent; } | |||
| AutoRemovingSourcePlayer* parent; | |||
| }; | |||
| //============================================================================== | |||
| AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource) | |||
| : manager (deviceManager), | |||
| deleteWhenDone (ownSource), | |||
| hasAddedCallback (false), | |||
| recursiveEntry (false) | |||
| { | |||
| } | |||
| void changeListenerCallback (ChangeBroadcaster* newSource) override | |||
| { | |||
| if (AudioTransportSource* currentTransport | |||
| = dynamic_cast<AudioTransportSource*> (getCurrentSource())) | |||
| { | |||
| ignoreUnused (newSource); | |||
| jassert (newSource == currentTransport); | |||
| if (! currentTransport->isPlaying()) | |||
| { | |||
| // this will call audioDeviceStopped! | |||
| manager.removeAudioCallback (this); | |||
| } | |||
| else if (! hasAddedCallback) | |||
| { | |||
| hasAddedCallback = true; | |||
| manager.addAudioCallback (this); | |||
| } | |||
| } | |||
| } | |||
| void audioDeviceStopped() override | |||
| { | |||
| if (! recursiveEntry) | |||
| { | |||
| ScopedValueSetter<bool> s (recursiveEntry, true, false); | |||
| manager.removeAudioCallback (this); | |||
| AudioSourcePlayer::audioDeviceStopped(); | |||
| if (MessageManager* mm = MessageManager::getInstanceWithoutCreating()) | |||
| { | |||
| if (mm->isThisTheMessageThread()) | |||
| delete this; | |||
| else | |||
| (new DeleteOnMessageThread (this))->post(); | |||
| } | |||
| } | |||
| } | |||
| void setSource (AudioTransportSource* newSource) | |||
| { | |||
| AudioSource* oldSource = getCurrentSource(); | |||
| if (AudioTransportSource* oldTransport = dynamic_cast<AudioTransportSource*> (oldSource)) | |||
| oldTransport->removeChangeListener (this); | |||
| if (newSource != nullptr) | |||
| newSource->addChangeListener (this); | |||
| AudioSourcePlayer::setSource (newSource); | |||
| if (deleteWhenDone) | |||
| delete oldSource; | |||
| } | |||
| private: | |||
| // only allow myself to be deleted when my audio callback has been removed | |||
| ~AutoRemovingSourcePlayer() | |||
| { | |||
| setSource (nullptr); | |||
| } | |||
| AudioDeviceManager& manager; | |||
| bool deleteWhenDone, hasAddedCallback, recursiveEntry; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
| }; | |||
| //============================================================================== | |||
| // An AudioSource which simply outputs a buffer | |||
| class AudioSampleBufferSource : public PositionableAudioSource | |||
| class AudioSampleBufferSource : public PositionableAudioSource | |||
| { | |||
| public: | |||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool shouldLoop, bool ownBuffer) | |||
| : position (0), | |||
| buffer (audioBuffer), | |||
| looping (shouldLoop), | |||
| deleteWhenDone (ownBuffer) | |||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer) | |||
| : buffer (audioBuffer, ownBuffer), | |||
| position (0), looping (false) | |||
| {} | |||
| ~AudioSampleBufferSource() | |||
| { | |||
| if (deleteWhenDone) | |||
| delete buffer; | |||
| } | |||
| //============================================================================== | |||
| void setNextReadPosition (int64 newPosition) override | |||
| { | |||
| @@ -1074,69 +1009,45 @@ public: | |||
| position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition)); | |||
| } | |||
| int64 getNextReadPosition() const override | |||
| { | |||
| return static_cast<int64> (position); | |||
| } | |||
| int64 getTotalLength() const override | |||
| { | |||
| return static_cast<int64> (buffer->getNumSamples()); | |||
| } | |||
| bool isLooping() const override | |||
| { | |||
| return looping; | |||
| } | |||
| int64 getNextReadPosition() const override { return static_cast<int64> (position); } | |||
| int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); } | |||
| void setLooping (bool shouldLoop) override | |||
| { | |||
| looping = shouldLoop; | |||
| } | |||
| bool isLooping() const override { return looping; } | |||
| void setLooping (bool shouldLoop) override { looping = shouldLoop; } | |||
| //============================================================================== | |||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override | |||
| { | |||
| ignoreUnused (samplesPerBlockExpected, sampleRate); | |||
| } | |||
| void releaseResources() override | |||
| {} | |||
| void prepareToPlay (int, double) override {} | |||
| void releaseResources() override {} | |||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||
| { | |||
| int max = jmin (buffer->getNumSamples() - position, bufferToFill.numSamples); | |||
| jassert (max >= 0); | |||
| { | |||
| int ch; | |||
| int maxInChannels = buffer->getNumChannels(); | |||
| int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), | |||
| jmax (maxInChannels, 2)); | |||
| bufferToFill.clearActiveBufferRegion(); | |||
| for (ch = 0; ch < maxOutChannels; ch++) | |||
| { | |||
| int inChannel = ch % maxInChannels; | |||
| const int bufferSize = buffer->getNumSamples(); | |||
| const int samplesNeeded = bufferToFill.numSamples; | |||
| const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); | |||
| if (max > 0) | |||
| bufferToFill.buffer->copyFrom (ch, bufferToFill.startSample, *buffer, inChannel, position, max); | |||
| } | |||
| if (samplesToCopy > 0) | |||
| { | |||
| const int maxInChannels = buffer->getNumChannels(); | |||
| const int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), jmax (maxInChannels, 2)); | |||
| for (; ch < bufferToFill.buffer->getNumChannels(); ++ch) | |||
| bufferToFill.buffer->clear (ch, bufferToFill.startSample, bufferToFill.numSamples); | |||
| for (int i = 0; i < maxOutChannels; ++i) | |||
| bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||
| i % maxInChannels, position, samplesToCopy); | |||
| } | |||
| position += max; | |||
| position += samplesNeeded; | |||
| if (looping) | |||
| position = position % buffer->getNumSamples(); | |||
| position %= bufferSize; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<AudioSampleBuffer> buffer; | |||
| int position; | |||
| AudioSampleBuffer* buffer; | |||
| bool looping, deleteWhenDone; | |||
| bool looping; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||
| }; | |||
| @@ -1165,38 +1076,38 @@ void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSiz | |||
| void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||
| { | |||
| playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
| if (reader != nullptr) | |||
| playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
| } | |||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
| { | |||
| if (buffer != nullptr) | |||
| playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished), true); | |||
| } | |||
| void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||
| { | |||
| if (audioSource != nullptr && currentAudioDevice != nullptr) | |||
| { | |||
| if (AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource)) | |||
| { | |||
| AutoRemovingSourcePlayer* player = new AutoRemovingSourcePlayer (*this, deleteWhenFinished); | |||
| player->setSource (transport); | |||
| } | |||
| else | |||
| { | |||
| AudioTransportSource* transportSource; | |||
| AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource); | |||
| if (transport == nullptr) | |||
| { | |||
| if (deleteWhenFinished) | |||
| { | |||
| AudioSourceOwningTransportSource* owningTransportSource = new AudioSourceOwningTransportSource(); | |||
| owningTransportSource->setSource (audioSource); | |||
| transportSource = owningTransportSource; | |||
| transport = new AudioSourceOwningTransportSource (audioSource); | |||
| } | |||
| else | |||
| { | |||
| transportSource = new AudioTransportSource; | |||
| transportSource->setSource (audioSource); | |||
| transport = new AudioTransportSource(); | |||
| transport->setSource (audioSource); | |||
| deleteWhenFinished = true; | |||
| } | |||
| // recursively call myself | |||
| playSound (transportSource, true); | |||
| transportSource->start(); | |||
| } | |||
| transport->start(); | |||
| new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); | |||
| } | |||
| else | |||
| { | |||
| @@ -1205,11 +1116,6 @@ void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool d | |||
| } | |||
| } | |||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
| { | |||
| playSound (new AudioSampleBufferSource (buffer, false, deleteWhenFinished), true); | |||
| } | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #if defined (JUCE_AUDIO_DEVICES_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
| #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 | |||
| @@ -31,10 +31,6 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| // Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
| // and your header search path must make it accessible to the module's files. | |||
| #include "AppConfig.h" | |||
| #include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
| #include "juce_audio_devices.h" | |||
| @@ -74,8 +74,7 @@ public: | |||
| int numBytesSoFar, | |||
| double timestamp) | |||
| { | |||
| // (this bit is just to avoid compiler warnings about unused variables) | |||
| (void) source; (void) messageData; (void) numBytesSoFar; (void) timestamp; | |||
| ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||
| } | |||
| }; | |||
| @@ -40,6 +40,16 @@ MidiOutput::MidiOutput(const String& midiName) | |||
| { | |||
| } | |||
| void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer) | |||
| { | |||
| MidiBuffer::Iterator i (buffer); | |||
| MidiMessage message; | |||
| int samplePosition; // Note: not actually used, so no need to initialise. | |||
| while (i.getNextEvent (message, samplePosition)) | |||
| sendMessageNow (message); | |||
| } | |||
| void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, | |||
| const double millisecondCounterToStartAt, | |||
| double samplesPerSecondForBuffer) | |||
| @@ -87,11 +87,12 @@ public: | |||
| /** Returns the name of this device. */ | |||
| const String& getName() const noexcept { return name; } | |||
| /** Makes this device output a midi message. | |||
| @see MidiMessage | |||
| */ | |||
| /** 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. | |||
| @@ -51,7 +51,7 @@ public: | |||
| void pushMidiData (const void* inputData, int numBytes, double time, | |||
| UserDataType* input, CallbackType& callback) | |||
| { | |||
| const uint8* d = static_cast <const uint8*> (inputData); | |||
| const uint8* d = static_cast<const uint8*> (inputData); | |||
| while (numBytes > 0) | |||
| { | |||
| @@ -22,6 +22,8 @@ | |||
| ============================================================================== | |||
| */ | |||
| #undef check | |||
| const char* const openSLTypeName = "Android OpenSL"; | |||
| bool isOpenSLAvailable() | |||
| @@ -43,7 +45,7 @@ public: | |||
| { | |||
| // OpenSL has piss-poor support for determining latency, so the only way I can find to | |||
| // get a number for this is by asking the AudioTrack/AudioRecord classes.. | |||
| AndroidAudioIODevice javaDevice (String::empty); | |||
| AndroidAudioIODevice javaDevice (deviceName); | |||
| // this is a total guess about how to calculate the latency, but seems to vaguely agree | |||
| // with the devices I've tested.. YMMV | |||
| @@ -550,7 +552,7 @@ private: | |||
| static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
| { | |||
| jassert (queue == static_cast<Player*> (context)->playerBufferQueue); (void) queue; | |||
| jassert (queue == static_cast<Player*> (context)->playerBufferQueue); ignoreUnused (queue); | |||
| static_cast<Player*> (context)->bufferList.bufferReturned(); | |||
| } | |||
| @@ -685,7 +687,7 @@ private: | |||
| static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
| { | |||
| jassert (queue == static_cast<Recorder*> (context)->recorderBufferQueue); (void) queue; | |||
| jassert (queue == static_cast<Recorder*> (context)->recorderBufferQueue); ignoreUnused (queue); | |||
| static_cast<Recorder*> (context)->bufferList.bufferReturned(); | |||
| } | |||
| @@ -393,13 +393,13 @@ private: | |||
| static void interruptionListenerCallback (void* client, UInt32 interruptionType) | |||
| { | |||
| const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices; | |||
| const Array<iOSAudioIODevice*>& activeDevices = static_cast<AudioSessionHolder*> (client)->activeDevices; | |||
| for (int i = activeDevices.size(); --i >= 0;) | |||
| activeDevices.getUnchecked(i)->interruptionListener (interruptionType); | |||
| } | |||
| Array <iOSAudioIODevice*> activeDevices; | |||
| Array<iOSAudioIODevice*> activeDevices; | |||
| }; | |||
| static AudioSessionHolder& getSessionHolder() | |||
| @@ -993,7 +993,7 @@ public: | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| if (ALSAAudioIODevice* d = dynamic_cast <ALSAAudioIODevice*> (device)) | |||
| if (ALSAAudioIODevice* d = dynamic_cast<ALSAAudioIODevice*> (device)) | |||
| return asInput ? inputIds.indexOf (d->inputId) | |||
| : outputIds.indexOf (d->outputId); | |||
| @@ -388,7 +388,7 @@ private: | |||
| if (callback != nullptr) | |||
| { | |||
| if ((numActiveInChans + numActiveOutChans) > 0) | |||
| callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans, | |||
| callback->audioDeviceIOCallback (const_cast<const float**> (inChans.getData()), numActiveInChans, | |||
| outChans, numActiveOutChans, numSamples); | |||
| } | |||
| else | |||
| @@ -437,7 +437,7 @@ private: | |||
| static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | |||
| { | |||
| if (JackAudioIODevice* device = static_cast <JackAudioIODevice*> (arg)) | |||
| if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg)) | |||
| device->updateActivePorts(); | |||
| } | |||
| @@ -470,7 +470,7 @@ private: | |||
| AudioIODeviceCallback* callback; | |||
| CriticalSection callbackLock; | |||
| HeapBlock <float*> inChans, outChans; | |||
| HeapBlock<float*> inChans, outChans; | |||
| int totalNumberOfInputChannels; | |||
| int totalNumberOfOutputChannels; | |||
| Array<void*> inputPorts, outputPorts; | |||
| @@ -557,7 +557,7 @@ public: | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| if (JackAudioIODevice* d = dynamic_cast <JackAudioIODevice*> (device)) | |||
| if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device)) | |||
| return asInput ? inputIds.indexOf (d->inputId) | |||
| : outputIds.indexOf (d->outputId); | |||
| @@ -136,7 +136,7 @@ private: | |||
| HeapBlock<pollfd> pfd ((size_t) numPfds); | |||
| snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); | |||
| HeapBlock <uint8> buffer (maxEventSize); | |||
| HeapBlock<uint8> buffer (maxEventSize); | |||
| while (! threadShouldExit()) | |||
| { | |||
| @@ -169,7 +169,7 @@ void AudioCDReader::refreshTrackLengths() | |||
| { | |||
| XmlDocument doc (toc); | |||
| const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples); | |||
| (void) error; // could be logged.. | |||
| ignoreUnused (error); // could be logged.. | |||
| lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst(); | |||
| } | |||
| @@ -155,9 +155,7 @@ public: | |||
| outputLatency (0), | |||
| bitDepth (32), | |||
| callback (nullptr), | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 | |||
| audioProcID (0), | |||
| #endif | |||
| deviceID (id), | |||
| started (false), | |||
| sampleRate (0), | |||
| @@ -625,11 +623,7 @@ public: | |||
| if (deviceID != 0) | |||
| { | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
| if (OK (AudioDeviceAddIOProc (deviceID, audioIOProc, this))) | |||
| #else | |||
| if (OK (AudioDeviceCreateIOProcID (deviceID, audioIOProc, this, &audioProcID))) | |||
| #endif | |||
| { | |||
| if (OK (AudioDeviceStart (deviceID, audioIOProc))) | |||
| { | |||
| @@ -637,12 +631,8 @@ public: | |||
| } | |||
| else | |||
| { | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
| OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); | |||
| #else | |||
| OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); | |||
| audioProcID = 0; | |||
| #endif | |||
| } | |||
| } | |||
| } | |||
| @@ -669,13 +659,8 @@ public: | |||
| && ! leaveInterruptRunning) | |||
| { | |||
| OK (AudioDeviceStop (deviceID, audioIOProc)); | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
| OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); | |||
| #else | |||
| OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); | |||
| audioProcID = 0; | |||
| #endif | |||
| started = false; | |||
| @@ -793,9 +778,7 @@ public: | |||
| Array<double> sampleRates; | |||
| Array<int> bufferSizes; | |||
| AudioIODeviceCallback* callback; | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 | |||
| AudioDeviceIOProcID audioProcID; | |||
| #endif | |||
| private: | |||
| CriticalSection callbackLock; | |||
| @@ -37,7 +37,7 @@ namespace CoreMidiHelpers | |||
| Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); | |||
| #endif | |||
| (void) lineNum; | |||
| ignoreUnused (lineNum); | |||
| return false; | |||
| } | |||
| @@ -67,7 +67,7 @@ namespace ASIODebugging | |||
| #else | |||
| static void dummyLog() {} | |||
| #define JUCE_ASIO_LOG(msg) ASIODebugging::dummyLog() | |||
| #define JUCE_ASIO_LOG_ERROR(msg, errNum) (void) errNum; ASIODebugging::dummyLog() | |||
| #define JUCE_ASIO_LOG_ERROR(msg, errNum) ignoreUnused (errNum); ASIODebugging::dummyLog() | |||
| #endif | |||
| } | |||
| @@ -690,31 +690,24 @@ public: | |||
| JUCE_ASIO_LOG ("showing control panel"); | |||
| bool done = false; | |||
| insideControlPanelModalLoop = true; | |||
| JUCE_TRY | |||
| { | |||
| // are there are devices that need to be closed before showing their control panel? | |||
| // close(); | |||
| insideControlPanelModalLoop = true; | |||
| const uint32 started = Time::getMillisecondCounter(); | |||
| const uint32 started = Time::getMillisecondCounter(); | |||
| if (asioObject != nullptr) | |||
| { | |||
| asioObject->controlPanel(); | |||
| if (asioObject != nullptr) | |||
| { | |||
| asioObject->controlPanel(); | |||
| const int spent = (int) Time::getMillisecondCounter() - (int) started; | |||
| const int spent = (int) Time::getMillisecondCounter() - (int) started; | |||
| JUCE_ASIO_LOG ("spent: " + String (spent)); | |||
| JUCE_ASIO_LOG ("spent: " + String (spent)); | |||
| if (spent > 300) | |||
| { | |||
| shouldUsePreferredSize = true; | |||
| done = true; | |||
| } | |||
| if (spent > 300) | |||
| { | |||
| shouldUsePreferredSize = true; | |||
| done = true; | |||
| } | |||
| } | |||
| JUCE_CATCH_ALL | |||
| insideControlPanelModalLoop = false; | |||
| return done; | |||
| @@ -785,7 +778,7 @@ private: | |||
| HeapBlock<ASIOSampleFormat> inputFormat, outputFormat; | |||
| WaitableEvent event1; | |||
| HeapBlock <float> tempBuffer; | |||
| HeapBlock<float> tempBuffer; | |||
| int volatile bufferIndex, numActiveInputChans, numActiveOutputChans; | |||
| bool deviceIsOpen, isStarted, buffersCreated; | |||
| @@ -367,7 +367,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
| hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
| HeapBlock <byte> buffer (bytesPerBlock); | |||
| HeapBlock<byte> buffer (bytesPerBlock); | |||
| AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||
| int samplesDone = 0; | |||
| @@ -1027,7 +1027,7 @@ AudioCDReader::AudioCDReader (void* handle_) | |||
| AudioCDReader::~AudioCDReader() | |||
| { | |||
| using namespace CDReaderHelpers; | |||
| CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
| CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
| delete device; | |||
| } | |||
| @@ -1035,7 +1035,7 @@ bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int sta | |||
| int64 startSampleInFile, int numSamples) | |||
| { | |||
| using namespace CDReaderHelpers; | |||
| CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
| CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
| bool ok = true; | |||
| @@ -1131,7 +1131,7 @@ bool AudioCDReader::isCDStillPresent() const | |||
| { | |||
| using namespace CDReaderHelpers; | |||
| TOC toc = { 0 }; | |||
| return static_cast <CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc); | |||
| return static_cast<CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc); | |||
| } | |||
| void AudioCDReader::refreshTrackLengths() | |||
| @@ -1142,7 +1142,7 @@ void AudioCDReader::refreshTrackLengths() | |||
| TOC toc = { 0 }; | |||
| if (static_cast <CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc)) | |||
| if (static_cast<CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc)) | |||
| { | |||
| int numTracks = 1 + toc.lastTrack - toc.firstTrack; | |||
| @@ -1174,7 +1174,7 @@ int AudioCDReader::getLastIndex() const | |||
| int AudioCDReader::getIndexAt (int samplePos) | |||
| { | |||
| using namespace CDReaderHelpers; | |||
| CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
| CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
| const int frameNeeded = samplePos / samplesPerFrame; | |||
| @@ -1255,7 +1255,7 @@ Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||
| if (needToScan) | |||
| { | |||
| CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
| CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
| int pos = trackStart; | |||
| int last = -1; | |||
| @@ -1305,5 +1305,5 @@ Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||
| void AudioCDReader::ejectDisk() | |||
| { | |||
| using namespace CDReaderHelpers; | |||
| static_cast <CDDeviceWrapper*> (handle)->deviceHandle.openDrawer (true); | |||
| static_cast<CDDeviceWrapper*> (handle)->deviceHandle.openDrawer (true); | |||
| } | |||
| @@ -182,11 +182,9 @@ namespace DSoundLogging | |||
| } | |||
| } | |||
| #define CATCH JUCE_CATCH_EXCEPTION | |||
| #define JUCE_DS_LOG(a) DSoundLogging::logMessage(a); | |||
| #define JUCE_DS_LOG_ERROR(a) DSoundLogging::logError(a, __LINE__); | |||
| #else | |||
| #define CATCH JUCE_CATCH_ALL | |||
| #define JUCE_DS_LOG(a) | |||
| #define JUCE_DS_LOG_ERROR(a) | |||
| #endif | |||
| @@ -251,7 +249,7 @@ public: | |||
| { | |||
| JUCE_DS_LOG ("closing output: " + name); | |||
| HRESULT hr = pOutputBuffer->Stop(); | |||
| JUCE_DS_LOG_ERROR (hr); (void) hr; | |||
| JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); | |||
| pOutputBuffer->Release(); | |||
| pOutputBuffer = nullptr; | |||
| @@ -354,7 +352,7 @@ public: | |||
| hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */); | |||
| if (SUCCEEDED (hr)) | |||
| return String::empty; | |||
| return String(); | |||
| } | |||
| } | |||
| } | |||
| @@ -534,7 +532,7 @@ public: | |||
| { | |||
| JUCE_DS_LOG ("closing input: " + name); | |||
| HRESULT hr = pInputBuffer->Stop(); | |||
| JUCE_DS_LOG_ERROR (hr); (void) hr; | |||
| JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); | |||
| pInputBuffer->Release(); | |||
| pInputBuffer = nullptr; | |||
| @@ -597,7 +595,7 @@ public: | |||
| hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */); | |||
| if (SUCCEEDED (hr)) | |||
| return String::empty; | |||
| return String(); | |||
| } | |||
| } | |||
| @@ -1226,7 +1224,7 @@ public: | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| if (DSoundAudioIODevice* const d = dynamic_cast <DSoundAudioIODevice*> (device)) | |||
| if (DSoundAudioIODevice* const d = dynamic_cast<DSoundAudioIODevice*> (device)) | |||
| return asInput ? d->inputDeviceIndex | |||
| : d->outputDeviceIndex; | |||
| @@ -116,7 +116,7 @@ public: | |||
| static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, | |||
| DWORD_PTR midiMessage, DWORD_PTR timeStamp) | |||
| { | |||
| MidiInCollector* const collector = reinterpret_cast <MidiInCollector*> (dwInstance); | |||
| MidiInCollector* const collector = reinterpret_cast<MidiInCollector*> (dwInstance); | |||
| if (activeMidiCollectors.contains (collector)) | |||
| { | |||
| @@ -270,8 +270,8 @@ MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const call | |||
| } | |||
| } | |||
| ScopedPointer <MidiInput> in (new MidiInput (name)); | |||
| ScopedPointer <MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||
| ScopedPointer<MidiInput> in (new MidiInput (name)); | |||
| ScopedPointer<MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||
| HMIDIIN h; | |||
| MMRESULT err = midiInOpen (&h, deviceId, | |||
| @@ -32,7 +32,7 @@ namespace WasapiClasses | |||
| void logFailure (HRESULT hr) | |||
| { | |||
| (void) hr; | |||
| ignoreUnused (hr); | |||
| jassert (hr != (HRESULT) 0x800401f0); // If you hit this, it means you're trying to call from | |||
| // a thread which hasn't been initialised with CoInitialize(). | |||
| @@ -1,26 +0,0 @@ | |||
| /* | |||
| * Carla Juce setup | |||
| * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_FORMATS_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_FORMATS_H_INCLUDED | |||
| #include "juce_audio_basics.h" | |||
| #include "juce_audio_formats/AppConfig.h" | |||
| #include "juce_audio_formats/juce_audio_formats.h" | |||
| #endif // CARLA_JUCE_AUDIO_FORMATS_H_INCLUDED | |||
| @@ -1,53 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| Build options for juce_audio_formats static library | |||
| ============================================================================== | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_FORMATS_APPCONFIG_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_FORMATS_APPCONFIG_H_INCLUDED | |||
| #include "../juce_audio_basics/AppConfig.h" | |||
| //============================================================================= | |||
| /** 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 1 | |||
| /** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT | |||
| Enables the Windows Media SDK codecs. | |||
| */ | |||
| #define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | |||
| #endif // CARLA_JUCE_AUDIO_FORMATS_APPCONFIG_H_INCLUDED | |||
| @@ -10,7 +10,7 @@ include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_FORMATS_FLAGS) -w | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_FORMATS_FLAGS) -I.. | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -531,7 +531,7 @@ public: | |||
| } | |||
| else if (type == chunkName ("INST")) | |||
| { | |||
| HeapBlock <InstChunk> inst; | |||
| HeapBlock<InstChunk> inst; | |||
| inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | |||
| input->read (inst, (int) length); | |||
| inst->copyTo (metadataValues); | |||
| @@ -715,7 +715,7 @@ private: | |||
| using namespace AiffFileHelpers; | |||
| const bool couldSeekOk = output->setPosition (headerPosition); | |||
| (void) couldSeekOk; | |||
| ignoreUnused (couldSeekOk); | |||
| // if this fails, you've given it an output stream that can't seek! It needs | |||
| // to be able to seek back to write the header | |||
| @@ -972,7 +972,7 @@ bool AiffAudioFormat::canHandleFile (const File& f) | |||
| AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) | |||
| { | |||
| ScopedPointer <AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream)); | |||
| ScopedPointer<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream)); | |||
| if (w->sampleRate > 0 && w->numChannels > 0) | |||
| return w.release(); | |||
| @@ -1003,7 +1003,7 @@ AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, | |||
| const StringPairArray& metadataValues, | |||
| int /*qualityOptionIndex*/) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
| return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, (unsigned int) bitsPerSample, metadataValues); | |||
| return nullptr; | |||
| @@ -433,7 +433,7 @@ public: | |||
| memcpy (buffer + 18, info.md5sum, 16); | |||
| const bool seekOk = output->setPosition (4); | |||
| (void) seekOk; | |||
| ignoreUnused (seekOk); | |||
| // if this fails, you've given it an output stream that can't seek! It needs | |||
| // to be able to seek back to write the header | |||
| @@ -536,7 +536,7 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out, | |||
| const StringPairArray& /*metadataValues*/, | |||
| int qualityOptionIndex) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
| { | |||
| ScopedPointer<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels, | |||
| (uint32) bitsPerSample, qualityOptionIndex)); | |||
| @@ -110,7 +110,7 @@ private: | |||
| if (cp.start (processArgs)) | |||
| { | |||
| const String childOutput (cp.readAllProcessOutput()); | |||
| DBG (childOutput); (void) childOutput; | |||
| DBG (childOutput); ignoreUnused (childOutput); | |||
| cp.waitForProcessToFinish (10000); | |||
| return tempMP3.getFile().getSize() > 0; | |||
| @@ -205,6 +205,9 @@ AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* stream | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) | |||
| { | |||
| if (streamToWriteTo == nullptr) | |||
| return nullptr; | |||
| int vbr = 4; | |||
| int cbr = 0; | |||
| @@ -1613,7 +1613,7 @@ private: | |||
| headerParsed = sideParsed = dataParsed = isFreeFormat = wasFreeFormat = false; | |||
| lastFrameSize = -1; | |||
| needToSyncBitStream = true; | |||
| frameSize = sideInfoSize = dataSize = frameSize = bitIndex = 0; | |||
| frameSize = sideInfoSize = dataSize = bitIndex = 0; | |||
| lastFrameSizeNoPadding = bufferSpaceIndex = 0; | |||
| bufferPointer = bufferSpace[bufferSpaceIndex] + 512; | |||
| synthBo = 1; | |||
| @@ -3020,7 +3020,7 @@ public: | |||
| } | |||
| const int numToCopy = jmin (decodedEnd - decodedStart, numSamples); | |||
| float* const* const dst = reinterpret_cast <float**> (destSamples); | |||
| float* const* const dst = reinterpret_cast<float**> (destSamples); | |||
| memcpy (dst[0] + startOffsetInDestBuffer, decoded0 + decodedStart, sizeof (float) * (size_t) numToCopy); | |||
| if (numDestChannels > 1 && dst[1] != nullptr) | |||
| @@ -428,7 +428,7 @@ private: | |||
| const String s (metadata [name]); | |||
| if (s.isNotEmpty()) | |||
| vorbis_comment_add_tag (&vc, vorbisName, const_cast <char*> (s.toRawUTF8())); | |||
| vorbis_comment_add_tag (&vc, vorbisName, const_cast<char*> (s.toRawUTF8())); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggWriter) | |||
| @@ -483,8 +483,12 @@ AudioFormatWriter* OggVorbisAudioFormat::createWriterFor (OutputStream* out, | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) | |||
| { | |||
| ScopedPointer <OggWriter> w (new OggWriter (out, sampleRate, numChannels, | |||
| (unsigned int) bitsPerSample, qualityOptionIndex, metadataValues)); | |||
| if (out == nullptr) | |||
| return nullptr; | |||
| ScopedPointer<OggWriter> w (new OggWriter (out, sampleRate, numChannels, | |||
| (unsigned int) bitsPerSample, | |||
| qualityOptionIndex, metadataValues)); | |||
| return w->ok ? w.release() : nullptr; | |||
| } | |||
| @@ -142,7 +142,7 @@ public: | |||
| if (err != noErr) | |||
| return; | |||
| HeapBlock <AudioChannelLayout> qt_audio_channel_layout; | |||
| HeapBlock<AudioChannelLayout> qt_audio_channel_layout; | |||
| qt_audio_channel_layout.calloc (output_layout_size, 1); | |||
| MovieAudioExtractionGetProperty (extractor, | |||
| @@ -322,8 +322,8 @@ private: | |||
| Thread::ThreadID lastThreadId; | |||
| MovieAudioExtractionRef extractor; | |||
| AudioStreamBasicDescription inputStreamDesc; | |||
| HeapBlock <AudioBufferList> bufferList; | |||
| HeapBlock <char> dataBuffer; | |||
| HeapBlock<AudioBufferList> bufferList; | |||
| HeapBlock<char> dataBuffer; | |||
| Handle dataHandle; | |||
| //============================================================================== | |||
| @@ -845,7 +845,7 @@ namespace WavFileHelpers | |||
| static MemoryBlock createFrom (const StringPairArray& values) | |||
| { | |||
| const String ISRC (values.getValue (WavAudioFormat::ISRC, String::empty)); | |||
| const String ISRC (values.getValue (WavAudioFormat::ISRC, String())); | |||
| MemoryOutputStream xml; | |||
| if (ISRC.isNotEmpty()) | |||
| @@ -1606,7 +1606,7 @@ AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sa | |||
| unsigned int numChannels, int bitsPerSample, | |||
| const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
| return new WavAudioFormatWriter (out, sampleRate, (unsigned int) numChannels, | |||
| (unsigned int) bitsPerSample, metadataValues); | |||
| @@ -151,7 +151,7 @@ AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileSt | |||
| // use them to open a file! | |||
| jassert (getNumKnownFormats() > 0); | |||
| ScopedPointer <InputStream> in (audioFileStream); | |||
| ScopedPointer<InputStream> in (audioFileStream); | |||
| if (in != nullptr) | |||
| { | |||
| @@ -126,8 +126,8 @@ public: | |||
| The stream that is passed-in must be capable of being repositioned so | |||
| that all the formats can have a go at opening it. | |||
| If none of the registered formats can open the stream, it'll return 0. If it | |||
| returns a reader, it's the caller's responsibility to delete the reader. | |||
| If none of the registered formats can open the stream, it'll return nullptr. | |||
| If it returns a reader, it's the caller's responsibility to delete the reader. | |||
| */ | |||
| AudioFormatReader* createReaderFor (InputStream* audioFileStream); | |||
| @@ -95,7 +95,7 @@ bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, | |||
| } | |||
| } | |||
| if (! write (const_cast <const int**> (buffers), numToDo)) | |||
| if (! write (const_cast<const int**> (buffers), numToDo)) | |||
| return false; | |||
| numSamplesToRead -= numToDo; | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #if defined (JUCE_AUDIO_FORMATS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
| #ifdef JUCE_AUDIO_FORMATS_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 | |||
| @@ -31,10 +31,6 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| // Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
| // and your header search path must make it accessible to the module's files. | |||
| #include "AppConfig.h" | |||
| #include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
| #include "juce_audio_formats.h" | |||
| @@ -93,7 +93,7 @@ void SamplerVoice::startNote (const int midiNoteNumber, | |||
| SynthesiserSound* s, | |||
| const int /*currentPitchWheelPosition*/) | |||
| { | |||
| if (const SamplerSound* const sound = dynamic_cast <const SamplerSound*> (s)) | |||
| if (const SamplerSound* const sound = dynamic_cast<const SamplerSound*> (s)) | |||
| { | |||
| pitchRatio = pow (2.0, (midiNoteNumber - sound->midiRootNote) / 12.0) | |||
| * sound->sourceSampleRate / getSampleRate(); | |||
| @@ -152,7 +152,7 @@ void SamplerVoice::controllerMoved (const int /*controllerNumber*/, | |||
| //============================================================================== | |||
| void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) | |||
| { | |||
| if (const SamplerSound* const playingSound = static_cast <SamplerSound*> (getCurrentlyPlayingSound().get())) | |||
| if (const SamplerSound* const playingSound = static_cast<SamplerSound*> (getCurrentlyPlayingSound().get())) | |||
| { | |||
| const float* const inL = playingSound->data->getReadPointer (0); | |||
| const float* const inR = playingSound->data->getNumChannels() > 1 | |||
| @@ -1,28 +0,0 @@ | |||
| /* | |||
| * Carla Juce setup | |||
| * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_PROCESSORS_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_PROCESSORS_H_INCLUDED | |||
| #include "juce_gui_basics.h" | |||
| #include "juce_audio_basics.h" | |||
| // patched to not use gui on non-win/mac | |||
| #include "juce_audio_processors/AppConfig.h" | |||
| #include "juce_audio_processors/juce_audio_processors.h" | |||
| #endif // CARLA_JUCE_AUDIO_PROCESSORS_H_INCLUDED | |||
| @@ -1,58 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| Build options for juce_audio_processors static library | |||
| ============================================================================== | |||
| */ | |||
| #ifndef CARLA_JUCE_AUDIO_PROCESSORS_APPCONFIG_H_INCLUDED | |||
| #define CARLA_JUCE_AUDIO_PROCESSORS_APPCONFIG_H_INCLUDED | |||
| #include "../juce_gui_basics/AppConfig.h" | |||
| #include "../juce_audio_basics/AppConfig.h" | |||
| //============================================================================= | |||
| /** 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 | |||
| */ | |||
| #ifndef VESTIGE_HEADER | |||
| # define JUCE_PLUGINHOST_VST 1 | |||
| #else | |||
| # define JUCE_PLUGINHOST_VST 0 | |||
| #endif | |||
| /** 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 JUCE_WINDOWS || JUCE_MAC | |||
| # 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 | |||
| */ | |||
| #if JUCE_MAC | |||
| # define JUCE_PLUGINHOST_AU 1 | |||
| #else | |||
| # define JUCE_PLUGINHOST_AU 0 | |||
| #endif | |||
| #define JUCE_PLUGINHOST_LADSPA 0 | |||
| // Fix MinGW VST3 build | |||
| #if JUCE_WINDOWS | |||
| # define _set_abort_behavior(...) | |||
| #endif | |||
| #endif // CARLA_JUCE_AUDIO_PROCESSORS_APPCONFIG_H_INCLUDED | |||
| @@ -10,12 +10,13 @@ include ../Makefile.mk | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_PROCESSORS_FLAGS) -I$(CWD)/includes/ladspa -I$(CWD)/includes/vst2 -I$(CWD)/includes/vst3 -w | |||
| BUILD_CXX_FLAGS += $(JUCE_AUDIO_PROCESSORS_FLAGS) -I$(CWD)/includes/ladspa -I$(CWD)/includes/vst2 -I$(CWD)/includes/vst3 -I.. | |||
| ifeq ($(CARLA_VESTIGE_HEADER),true) | |||
| BUILD_CXX_FLAGS += -DVESTIGE_HEADER | |||
| else | |||
| # needed by vst3 | |||
| BUILD_CXX_FLAGS += -w | |||
| ifeq ($(DEBUG),true) | |||
| BUILD_CXX_FLAGS += -DDEVELOPMENT -D_DEBUG | |||
| else | |||
| @@ -33,19 +33,19 @@ void AudioPluginFormatManager::addDefaultFormats() | |||
| for (int i = formats.size(); --i >= 0;) | |||
| { | |||
| #if JUCE_PLUGINHOST_VST | |||
| jassert (dynamic_cast <VSTPluginFormat*> (formats[i]) == nullptr); | |||
| jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST3 | |||
| jassert (dynamic_cast <VST3PluginFormat*> (formats[i]) == nullptr); | |||
| jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_AU && JUCE_MAC | |||
| jassert (dynamic_cast <AudioUnitPluginFormat*> (formats[i]) == nullptr); | |||
| jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX | |||
| jassert (dynamic_cast <LADSPAPluginFormat*> (formats[i]) == nullptr); | |||
| jassert (dynamic_cast<LADSPAPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| } | |||
| #endif | |||
| @@ -209,7 +209,7 @@ namespace AudioUnitFormatHelpers | |||
| if (manuString != 0 && CFGetTypeID (manuString) == CFStringGetTypeID()) | |||
| manufacturer = String::fromCFString ((CFStringRef) manuString); | |||
| const short resFileId = CFBundleOpenBundleResourceMap (bundleRef); | |||
| const ResFileRefNum resFileId = CFBundleOpenBundleResourceMap (bundleRef); | |||
| UseResFile (resFileId); | |||
| const OSType thngType = stringToOSType ("thng"); | |||
| @@ -379,8 +379,8 @@ public: | |||
| desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); | |||
| desc.manufacturerName = manufacturer; | |||
| desc.version = version; | |||
| desc.numInputChannels = getNumInputChannels(); | |||
| desc.numOutputChannels = getNumOutputChannels(); | |||
| desc.numInputChannels = getTotalNumInputChannels(); | |||
| desc.numOutputChannels = getTotalNumOutputChannels(); | |||
| desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); | |||
| } | |||
| @@ -540,7 +540,7 @@ public: | |||
| for (AudioUnitElement j = 0; j < numOutputBusChannels; ++j) | |||
| { | |||
| abl->mBuffers[j].mNumberChannels = 1; | |||
| abl->mBuffers[j].mDataByteSize = sizeof (float) * (size_t) numSamples; | |||
| abl->mBuffers[j].mDataByteSize = (UInt32) (sizeof (float) * (size_t) numSamples); | |||
| abl->mBuffers[j].mData = buffer.getWritePointer ((int) (i * numOutputBusChannels + j)); | |||
| } | |||
| } | |||
| @@ -578,7 +578,7 @@ public: | |||
| else | |||
| { | |||
| // Plugin not working correctly, so just bypass.. | |||
| for (int i = 0; i < getNumOutputChannels(); ++i) | |||
| for (int i = getTotalNumOutputChannels(); --i >= 0;) | |||
| buffer.clear (i, 0, buffer.getNumSamples()); | |||
| } | |||
| @@ -597,7 +597,7 @@ public: | |||
| //============================================================================== | |||
| const String getInputChannelName (int index) const override | |||
| { | |||
| if (isPositiveAndBelow (index, getNumInputChannels())) | |||
| if (isPositiveAndBelow (index, getTotalNumInputChannels())) | |||
| return "Input " + String (index + 1); | |||
| return String(); | |||
| @@ -605,14 +605,14 @@ public: | |||
| const String getOutputChannelName (int index) const override | |||
| { | |||
| if (isPositiveAndBelow (index, getNumOutputChannels())) | |||
| if (isPositiveAndBelow (index, getTotalNumOutputChannels())) | |||
| return "Output " + String (index + 1); | |||
| return String(); | |||
| } | |||
| bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getNumInputChannels()); } | |||
| bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getNumOutputChannels()); } | |||
| bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumInputChannels()); } | |||
| bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } | |||
| //============================================================================== | |||
| int getNumParameters() override { return parameters.size(); } | |||
| @@ -841,9 +841,9 @@ public: | |||
| if (audioUnit != nullptr) | |||
| { | |||
| UInt32 dummy = 0, paramListSize = 0; | |||
| AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, | |||
| 0, &dummy, ¶mListSize); | |||
| UInt32 paramListSize = 0; | |||
| AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, | |||
| 0, ¶mListSize, nullptr); | |||
| if (paramListSize > 0) | |||
| { | |||
| @@ -855,7 +855,7 @@ public: | |||
| AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, | |||
| 0, ids, ¶mListSize); | |||
| for (int i = 0; i < numParams; ++i) | |||
| for (size_t i = 0; i < numParams; ++i) | |||
| { | |||
| AudioUnitParameterInfo info; | |||
| UInt32 sz = sizeof (info); | |||
| @@ -909,7 +909,7 @@ private: | |||
| CriticalSection lock; | |||
| bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared; | |||
| HeapBlock <AudioBufferList> outputBufferList; | |||
| HeapBlock<AudioBufferList> outputBufferList; | |||
| AudioTimeStamp timeStamp; | |||
| AudioSampleBuffer* currentBuffer; | |||
| AudioUnitElement numInputBusChannels, numOutputBusChannels, numInputBusses, numOutputBusses; | |||
| @@ -972,12 +972,7 @@ private: | |||
| kAudioUnitScope_Global, 0, &info, sizeof (info)); | |||
| } | |||
| AUEventListenerCreate (eventListenerCallback, this, | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4 | |||
| CFRunLoopGetMain(), | |||
| #else | |||
| nullptr, | |||
| #endif | |||
| AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), | |||
| kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); | |||
| for (int i = 0; i < parameters.size(); ++i) | |||
| @@ -1007,6 +1002,10 @@ private: | |||
| event.mEventType = kAudioUnitEvent_PropertyChange; | |||
| AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
| // Add a listener for parameter list changes | |||
| event.mArgument.mProperty.mPropertyID = kAudioUnitProperty_ParameterList; | |||
| AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
| } | |||
| } | |||
| @@ -1037,7 +1036,11 @@ private: | |||
| break; | |||
| default: | |||
| sendAllParametersChangedEvents(); | |||
| if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) | |||
| updateHostDisplay(); | |||
| else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) | |||
| sendAllParametersChangedEvents(); | |||
| break; | |||
| } | |||
| } | |||
| @@ -1127,24 +1130,24 @@ private: | |||
| OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, | |||
| UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const | |||
| { | |||
| AudioPlayHead* const ph = getPlayHead(); | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (ph != nullptr && ph->getCurrentPosition (result)) | |||
| if (AudioPlayHead* const ph = getPlayHead()) | |||
| { | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); | |||
| setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx | |||
| setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong | |||
| } | |||
| else | |||
| { | |||
| setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) 4); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) 4); | |||
| setIfNotNull (outCurrentMeasureDownBeat, 0); | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (ph->getCurrentPosition (result)) | |||
| { | |||
| setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); | |||
| setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong | |||
| return noErr; | |||
| } | |||
| } | |||
| setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) 4); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) 4); | |||
| setIfNotNull (outCurrentMeasureDownBeat, 0); | |||
| return noErr; | |||
| } | |||
| @@ -1152,34 +1155,34 @@ private: | |||
| Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, | |||
| Float64* outCycleStartBeat, Float64* outCycleEndBeat) | |||
| { | |||
| AudioPlayHead* const ph = getPlayHead(); | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (ph != nullptr && ph->getCurrentPosition (result)) | |||
| if (AudioPlayHead* const ph = getPlayHead()) | |||
| { | |||
| setIfNotNull (outIsPlaying, result.isPlaying); | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (outTransportStateChanged != nullptr) | |||
| if (ph->getCurrentPosition (result)) | |||
| { | |||
| *outTransportStateChanged = result.isPlaying != wasPlaying; | |||
| wasPlaying = result.isPlaying; | |||
| } | |||
| setIfNotNull (outIsPlaying, result.isPlaying); | |||
| setIfNotNull (outCurrentSampleInTimeLine, result.timeInSamples); | |||
| setIfNotNull (outIsCycling, false); | |||
| setIfNotNull (outCycleStartBeat, 0); | |||
| setIfNotNull (outCycleEndBeat, 0); | |||
| } | |||
| else | |||
| { | |||
| setIfNotNull (outIsPlaying, false); | |||
| setIfNotNull (outTransportStateChanged, false); | |||
| setIfNotNull (outCurrentSampleInTimeLine, 0); | |||
| setIfNotNull (outIsCycling, false); | |||
| setIfNotNull (outCycleStartBeat, 0); | |||
| setIfNotNull (outCycleEndBeat, 0); | |||
| if (outTransportStateChanged != nullptr) | |||
| { | |||
| *outTransportStateChanged = result.isPlaying != wasPlaying; | |||
| wasPlaying = result.isPlaying; | |||
| } | |||
| setIfNotNull (outCurrentSampleInTimeLine, result.timeInSamples); | |||
| setIfNotNull (outIsCycling, result.isLooping); | |||
| setIfNotNull (outCycleStartBeat, result.ppqLoopStart); | |||
| setIfNotNull (outCycleEndBeat, result.ppqLoopEnd); | |||
| return noErr; | |||
| } | |||
| } | |||
| setIfNotNull (outIsPlaying, false); | |||
| setIfNotNull (outTransportStateChanged, false); | |||
| setIfNotNull (outCurrentSampleInTimeLine, 0); | |||
| setIfNotNull (outIsCycling, false); | |||
| setIfNotNull (outCycleStartBeat, 0.0); | |||
| setIfNotNull (outCycleEndBeat, 0.0); | |||
| return noErr; | |||
| } | |||
| @@ -1193,7 +1196,7 @@ private: | |||
| } | |||
| static OSStatus renderMidiOutputCallback (void* hostRef, const AudioTimeStamp*, UInt32 /*midiOutNum*/, | |||
| const struct MIDIPacketList* pktlist) | |||
| const MIDIPacketList* pktlist) | |||
| { | |||
| return static_cast<AudioUnitPluginInstance*> (hostRef)->renderMidiOutput (pktlist); | |||
| } | |||
| @@ -1394,7 +1397,7 @@ private: | |||
| && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, | |||
| 0, &dataSize, &isWritable) == noErr) | |||
| { | |||
| HeapBlock <AudioUnitCocoaViewInfo> info; | |||
| HeapBlock<AudioUnitCocoaViewInfo> info; | |||
| info.calloc (dataSize, 1); | |||
| if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, | |||