Signed-off-by: falkTX <falktx@falktx.com>pull/1898/head
@@ -82,21 +82,6 @@ ifeq ($(HAVE_YSFX),true) | |||
3RD_LIBS += $(MODULEDIR)/ysfx.a | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
3RD_LIBS += $(MODULEDIR)/carla_juce.a | |||
3RD_LIBS += $(MODULEDIR)/juce_audio_basics.a | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
3RD_LIBS += $(MODULEDIR)/juce_audio_devices.a | |||
endif | |||
3RD_LIBS += $(MODULEDIR)/juce_audio_processors.a | |||
3RD_LIBS += $(MODULEDIR)/juce_core.a | |||
3RD_LIBS += $(MODULEDIR)/juce_data_structures.a | |||
3RD_LIBS += $(MODULEDIR)/juce_events.a | |||
3RD_LIBS += $(MODULEDIR)/juce_graphics.a | |||
3RD_LIBS += $(MODULEDIR)/juce_gui_basics.a | |||
3RD_LIBS += $(MODULEDIR)/juce_gui_extra.a | |||
endif | |||
ifeq ($(USING_RTAUDIO),true) | |||
3RD_LIBS += $(MODULEDIR)/rtaudio.a | |||
3RD_LIBS += $(MODULEDIR)/rtmidi.a | |||
@@ -41,13 +41,9 @@ else | |||
endif | |||
ifeq ($(HAVE_HYLIA),true) | |||
@printf -- "Link support: $(ANS_YES)\n" | |||
else | |||
ifeq ($(MACOS_OLD),true) | |||
@printf -- "Link support: $(ANS_NO) $(mZ)MacOS >= 10.8 only$(mE)\n" | |||
else | |||
@printf -- "Link support: $(ANS_NO) $(mZ)Linux, MacOS and Windows only$(mE)\n" | |||
endif | |||
endif | |||
ifeq ($(HAVE_LIBLO),true) | |||
@printf -- "OSC support: $(ANS_YES)\n" | |||
else | |||
@@ -55,12 +51,10 @@ else | |||
endif | |||
ifeq ($(WINDOWS),true) | |||
@printf -- "Binary detect: $(ANS_YES)\n" | |||
else | |||
ifeq ($(HAVE_LIBMAGIC),true) | |||
else ifeq ($(HAVE_LIBMAGIC),true) | |||
@printf -- "Binary detect: $(ANS_YES)\n" | |||
else | |||
@printf -- "Binary detect: $(ANS_NO) $(mS)Missing libmagic/file$(mE)\n" | |||
endif | |||
endif | |||
@printf -- "\n" | |||
@@ -94,11 +88,9 @@ else | |||
@printf -- "CoreAudio: $(ANS_NO) $(mZ)MacOS only$(mE)\n" | |||
endif | |||
ifeq ($(WINDOWS),true) | |||
@printf -- "ASIO: $(ANS_YES)\n" | |||
@printf -- "DirectSound: $(ANS_YES)\n" | |||
@printf -- "WASAPI: $(ANS_YES)\n" | |||
else | |||
@printf -- "ASIO: $(ANS_NO) $(mZ)Windows only$(mE)\n" | |||
@printf -- "DirectSound: $(ANS_NO) $(mZ)Windows only$(mE)\n" | |||
@printf -- "WASAPI: $(ANS_NO) $(mZ)Windows only$(mE)\n" | |||
endif | |||
@@ -118,26 +110,18 @@ endif | |||
ifeq ($(MACOS_OR_WINDOWS),true) | |||
@printf -- "VST2: $(ANS_YES) (with UI)\n" | |||
@printf -- "VST3: $(ANS_YES) (with UI)\n" | |||
else # MACOS_OR_WINDOWS | |||
ifeq ($(HAIKU),true) | |||
else ifeq ($(HAIKU),true) | |||
@printf -- "VST2: $(ANS_YES) (without UI)\n" | |||
@printf -- "VST3: $(ANS_YES) (without UI)\n" | |||
else # HAIKU | |||
ifeq ($(HAVE_X11),true) | |||
else ifeq ($(HAVE_X11),true) | |||
@printf -- "VST2: $(ANS_YES) (with UI)\n" | |||
@printf -- "VST3: $(ANS_YES) (with UI)\n" | |||
else # HAVE_X11 | |||
else | |||
@printf -- "VST2: $(ANS_YES) (without UI) $(mS)Missing X11$(mE)\n" | |||
@printf -- "VST3: $(ANS_YES) (without UI) $(mS)Missing X11$(mE)\n" | |||
endif # HAVE_X11 | |||
endif # HAIKU | |||
endif # MACOS_OR_WINDOWS | |||
endif | |||
ifeq ($(MACOS),true) | |||
ifeq ($(USING_JUCE),true) | |||
@printf -- "AU: $(ANS_YES) (with UI, using JUCE)\n" | |||
else # USING_JUCE | |||
@printf -- "AU: $(ANS_NO)\n" | |||
endif # USING_JUCE | |||
@printf -- "AU: $(ANS_YES) (with UI)\n" | |||
else # MACOS | |||
@printf -- "AU: $(ANS_NO) $(mZ)MacOS only$(mE)\n" | |||
endif # MACOS | |||
@@ -214,6 +214,7 @@ HAVE_QT4 = $(shell $(PKG_CONFIG) --exists QtCore QtGui && echo true) | |||
HAVE_QT5 = $(shell $(PKG_CONFIG) --exists Qt5Core Qt5Gui Qt5Widgets && \ | |||
$(PKG_CONFIG) --variable=qt_config Qt5Core | grep -q -v "static" && echo true) | |||
HAVE_QT5PKG = $(shell $(PKG_CONFIG) --silence-errors --variable=prefix Qt5OpenGLExtensions 1>/dev/null && echo true) | |||
HAVE_QT5BREW = $(shell test -e /usr/local/opt/qt5/bin/uic && echo true) | |||
HAVE_SNDFILE = $(shell $(PKG_CONFIG) --exists sndfile && echo true) | |||
ifeq ($(HAVE_FLUIDSYNTH),true) | |||
@@ -276,6 +277,8 @@ ifeq ($(HAVE_QT5),true) | |||
QT5_HOSTBINS = $(shell $(PKG_CONFIG) --variable=host_bins Qt5Core) | |||
else ifeq ($(HAVE_QT5PKG),true) | |||
QT5_HOSTBINS = $(shell $(PKG_CONFIG) --variable=prefix Qt5OpenGLExtensions)/bin | |||
else ifeq ($(HAVE_QT5BREW),true) | |||
QT5_HOSTBINS = /usr/local/opt/qt5/bin | |||
endif | |||
MOC_QT5 ?= $(QT5_HOSTBINS)/moc | |||
@@ -301,7 +304,7 @@ else ifeq ($(WINDOWS),true) | |||
HAVE_QT = true | |||
endif | |||
ifneq (,$(findstring true,$(HAVE_QT5)$(HAVE_QT5PKG))) | |||
ifneq (,$(findstring true,$(HAVE_QT5)$(HAVE_QT5PKG)$(HAVE_QT5BREW))) | |||
HAVE_THEME = true | |||
endif | |||
@@ -314,7 +317,7 @@ PYUIC5 ?= $(shell which pyuic5 2>/dev/null) | |||
ifneq ($(PYUIC5),) | |||
ifneq ($(PYRCC5),) | |||
HAVE_PYQT = true | |||
ifneq (,$(findstring true,$(HAVE_QT5)$(HAVE_QT5PKG))) | |||
ifneq (,$(findstring true,$(HAVE_QT5)$(HAVE_QT5PKG)$(HAVE_QT5BREW))) | |||
HAVE_FRONTEND = true | |||
endif | |||
endif | |||
@@ -326,24 +329,14 @@ endif | |||
PYRCC ?= $(PYRCC5) | |||
PYUIC ?= $(PYUIC5) | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Set USING_JUCE | |||
ifeq ($(MACOS_OR_WINDOWS),true) | |||
USING_JUCE = true | |||
USING_JUCE_AUDIO_DEVICES = true | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Set USING_RTAUDIO | |||
ifneq ($(HAIKU),true) | |||
ifneq ($(WASM),true) | |||
ifneq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
USING_RTAUDIO = true | |||
endif | |||
endif | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Disable features for static plugin target | |||
@@ -360,11 +353,10 @@ HAVE_PYQT = false | |||
HAVE_QT4 = false | |||
HAVE_QT5 = false | |||
HAVE_QT5PKG = false | |||
HAVE_QT5BREW = false | |||
HAVE_SDL = false | |||
HAVE_SDL1 = false | |||
HAVE_SDL2 = false | |||
USING_JUCE = false | |||
USING_JUCE_AUDIO_DEVICES = false | |||
USING_RTAUDIO = false | |||
endif | |||
@@ -488,35 +480,6 @@ endif | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Set libs stuff (juce) | |||
ifeq ($(USING_JUCE),true) | |||
ifeq ($(MACOS),true) | |||
JUCE_AUDIO_BASICS_LIBS = -framework Accelerate | |||
JUCE_AUDIO_DEVICES_LIBS = -framework AppKit -framework AudioToolbox -framework CoreAudio -framework CoreMIDI | |||
JUCE_AUDIO_FORMATS_LIBS = -framework AudioToolbox -framework CoreFoundation | |||
JUCE_AUDIO_PROCESSORS_LIBS = -framework AudioToolbox -framework AudioUnit -framework CoreAudio -framework CoreAudioKit -framework Cocoa | |||
JUCE_CORE_LIBS = -framework AppKit | |||
JUCE_EVENTS_LIBS = -framework AppKit | |||
JUCE_GRAPHICS_LIBS = -framework Cocoa -framework QuartzCore | |||
JUCE_GUI_BASICS_LIBS = -framework Cocoa | |||
JUCE_GUI_EXTRA_LIBS = -framework IOKit | |||
else ifeq ($(WINDOWS),true) | |||
JUCE_AUDIO_BASICS_LIBS = | |||
JUCE_AUDIO_DEVICES_LIBS = -lwinmm -lole32 | |||
JUCE_AUDIO_FORMATS_LIBS = | |||
JUCE_AUDIO_PROCESSORS_LIBS = | |||
JUCE_CORE_LIBS = -luuid -lwsock32 -lwininet -lversion -lole32 -lws2_32 -loleaut32 -limm32 -lcomdlg32 -lshlwapi -lrpcrt4 -lwinmm | |||
JUCE_EVENTS_LIBS = -lole32 | |||
JUCE_GRAPHICS_LIBS = -lgdi32 | |||
JUCE_GUI_BASICS_LIBS = -lgdi32 -limm32 -lcomdlg32 -lole32 | |||
JUCE_GUI_EXTRA_LIBS = | |||
endif | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Set libs stuff (rtaudio) | |||
@@ -650,17 +613,6 @@ STATIC_CARLA_PLUGIN_LIBS += $(RTMEMPOOL_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(WATER_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(YSFX_LIBS) | |||
ifeq ($(USING_JUCE),true) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_AUDIO_BASICS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_AUDIO_FORMATS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_AUDIO_PROCESSORS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_CORE_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_EVENTS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_GRAPHICS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_GUI_BASICS_LIBS) | |||
STATIC_CARLA_PLUGIN_LIBS += $(JUCE_GUI_EXTRA_LIBS) | |||
endif | |||
ifeq ($(EXTERNAL_PLUGINS),true) | |||
ifneq ($(DEBUG),true) | |||
ifneq ($(TESTBUILD),true) | |||
@@ -237,18 +237,6 @@ ifeq ($(HAVE_YSFX),true) | |||
BASE_FLAGS += -DHAVE_YSFX | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
BASE_FLAGS += -DUSING_JUCE | |||
BASE_FLAGS += -DJUCE_APP_CONFIG_HEADER='"AppConfig.h"' | |||
ifeq ($(WINDOWS),true) | |||
BASE_FLAGS += -D_WIN32_WINNT=0x0600 | |||
endif | |||
endif | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
BASE_FLAGS += -DUSING_JUCE_AUDIO_DEVICES | |||
endif | |||
ifeq ($(USING_RTAUDIO),true) | |||
BASE_FLAGS += -DUSING_RTAUDIO | |||
endif | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#ifndef CARLA_ENGINE_HPP_INCLUDED | |||
#define CARLA_ENGINE_HPP_INCLUDED | |||
@@ -52,35 +38,30 @@ enum EngineType { | |||
*/ | |||
kEngineTypeJack = 1, | |||
/*! | |||
* JUCE engine type, used to provide Native Audio and MIDI support. | |||
*/ | |||
kEngineTypeJuce = 2, | |||
/*! | |||
* RtAudio engine type, used to provide Native Audio and MIDI support. | |||
*/ | |||
kEngineTypeRtAudio = 3, | |||
kEngineTypeRtAudio = 2, | |||
/*! | |||
* SDL engine type, used to provide Native Audio support. | |||
*/ | |||
kEngineTypeSDL = 4, | |||
kEngineTypeSDL = 3, | |||
/*! | |||
* Plugin engine type, used to export the engine as a plugin. | |||
*/ | |||
kEngineTypePlugin = 5, | |||
kEngineTypePlugin = 4, | |||
/*! | |||
* Bridge engine type, used in BridgePlugin class. | |||
*/ | |||
kEngineTypeBridge = 6, | |||
kEngineTypeBridge = 5, | |||
/*! | |||
* Dummy engine type, does not send audio or MIDI anywhere. | |||
*/ | |||
kEngineTypeDummy = 7 | |||
kEngineTypeDummy = 6 | |||
}; | |||
/*! | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2020 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#ifndef CARLA_ENGINE_INIT_HPP_INCLUDED | |||
#define CARLA_ENGINE_INIT_HPP_INCLUDED | |||
@@ -60,14 +46,6 @@ CarlaEngine* newBridge(const char* audioPoolBaseName, | |||
const char* nonRtClientBaseName, | |||
const char* nonRtServerBaseName); | |||
// Juce | |||
CarlaEngine* newJuce(AudioApi api); | |||
uint getJuceApiCount(); | |||
const char* getJuceApiName(uint index); | |||
const char* const* getJuceApiDeviceNames(uint index); | |||
const EngineDriverDeviceInfo* getJuceDeviceInfo(uint index, const char* deviceName); | |||
bool showJuceDeviceControlPanel(uint index, const char* deviceName); | |||
// RtAudio | |||
CarlaEngine* newRtAudio(AudioApi api); | |||
uint getRtAudioApiCount(); | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#ifndef CARLA_HOST_H_INCLUDED | |||
#define CARLA_HOST_H_INCLUDED | |||
@@ -1173,11 +1159,6 @@ CARLA_API_EXPORT void carla_nsm_ready(CarlaHostHandle handle, NsmCallbackOpcode | |||
*/ | |||
CARLA_API_EXPORT const char* carla_get_complete_license_text(void); | |||
/*! | |||
* Get the juce version used in the current Carla build. | |||
*/ | |||
CARLA_API_EXPORT const char* carla_get_juce_version(void); | |||
/*! | |||
* Get the list of supported file extensions in carla_load_file(). | |||
*/ | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2023 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#ifndef CARLA_PLUGIN_HPP_INCLUDED | |||
#define CARLA_PLUGIN_HPP_INCLUDED | |||
@@ -986,7 +972,6 @@ public: | |||
static CarlaPluginPtr newJSFX(const Initializer& init); | |||
static CarlaPluginPtr newCLAP(const Initializer& init); | |||
static CarlaPluginPtr newJuce(const Initializer& init, const char* format); | |||
static CarlaPluginPtr newFluidSynth(const Initializer& init, PluginType ptype, bool use16Outs); | |||
static CarlaPluginPtr newSFZero(const Initializer& init); | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Standalone | |||
* Copyright (C) 2011-2023 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
// TODO: | |||
// Check carla_stderr2("Engine is not running"); <= prepend func name and args | |||
@@ -30,10 +16,6 @@ | |||
#include "water/files/File.h" | |||
#if defined(USING_JUCE) && !defined(BUILD_BRIDGE) | |||
# include "carla_juce/carla_juce.h" | |||
#endif | |||
#define CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(cond, msg, ret) \ | |||
if (! (cond)) { \ | |||
carla_stderr2("%s: " msg, __FUNCTION__); \ | |||
@@ -404,10 +386,6 @@ bool carla_engine_init(CarlaHostHandle handle, const char* driverName, const cha | |||
carla_setenv("WINEASIO_CLIENT_NAME", clientName); | |||
#endif | |||
#if defined(USING_JUCE) && !defined(BUILD_BRIDGE) | |||
CarlaJUCE::initialiseJuce_GUI(); | |||
#endif | |||
CarlaHostStandalone& shandle((CarlaHostStandalone&)*handle); | |||
CarlaEngine* const engine = CarlaEngine::newDriverByName(driverName); | |||
@@ -454,9 +432,6 @@ bool carla_engine_init(CarlaHostHandle handle, const char* driverName, const cha | |||
shandle.lastError = engine->getLastError(); | |||
shandle.engine = nullptr; | |||
delete engine; | |||
#if defined(USING_JUCE) && !defined(BUILD_BRIDGE) | |||
CarlaJUCE::shutdownJuce_GUI(); | |||
#endif | |||
return false; | |||
} | |||
} | |||
@@ -534,9 +509,6 @@ bool carla_engine_close(CarlaHostHandle handle) | |||
shandle.engine = nullptr; | |||
delete engine; | |||
#if defined(USING_JUCE) && !defined(BUILD_BRIDGE) | |||
CarlaJUCE::shutdownJuce_GUI(); | |||
#endif | |||
return closed; | |||
} | |||
@@ -545,11 +517,6 @@ void carla_engine_idle(CarlaHostHandle handle) | |||
CARLA_SAFE_ASSERT_RETURN(handle->engine != nullptr && handle->isStandalone,); | |||
handle->engine->idle(); | |||
#if defined(USING_JUCE) && !defined(BUILD_BRIDGE) | |||
if (handle->isStandalone) | |||
CarlaJUCE::idleJuce_GUI(); | |||
#endif | |||
} | |||
bool carla_is_engine_running(CarlaHostHandle handle) | |||
@@ -347,11 +347,6 @@ CARLA_PLUGIN_EXPORT const CarlaCachedPluginInfo* carla_get_cached_plugin_info(Pl | |||
*/ | |||
CARLA_PLUGIN_EXPORT const char* carla_get_complete_license_text(void); | |||
/*! | |||
* @deprecated do not use | |||
*/ | |||
CARLA_PLUGIN_EXPORT const char* carla_get_juce_version(void); | |||
/*! | |||
* Get the list of supported file extensions in carla_load_file(). | |||
*/ | |||
@@ -373,24 +368,6 @@ CARLA_PLUGIN_EXPORT const char* carla_get_library_filename(void); | |||
CARLA_PLUGIN_EXPORT const char* carla_get_library_folder(void); | |||
#endif | |||
/* -------------------------------------------------------------------------------------------------------------------- | |||
* JUCE */ | |||
/*! | |||
* @deprecated do not use | |||
*/ | |||
CARLA_PLUGIN_EXPORT void carla_juce_init(void); | |||
/*! | |||
* @deprecated do not use | |||
*/ | |||
CARLA_PLUGIN_EXPORT void carla_juce_idle(void); | |||
/*! | |||
* @deprecated do not use | |||
*/ | |||
CARLA_PLUGIN_EXPORT void carla_juce_cleanup(void); | |||
/* -------------------------------------------------------------------------------------------------------------------- | |||
* pipes */ | |||
@@ -54,21 +54,6 @@ ifeq ($(HAVE_YSFX),true) | |||
STANDALONE_LIBS += $(MODULEDIR)/ysfx.a | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
STANDALONE_LIBS += $(MODULEDIR)/carla_juce.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_audio_basics.a | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_audio_devices.a | |||
endif | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_audio_processors.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_core.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_data_structures.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_events.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_graphics.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_gui_basics.a | |||
STANDALONE_LIBS += $(MODULEDIR)/juce_gui_extra.a | |||
endif | |||
ifeq ($(USING_RTAUDIO),true) | |||
STANDALONE_LIBS += $(MODULEDIR)/rtaudio.a | |||
STANDALONE_LIBS += $(MODULEDIR)/rtmidi.a | |||
@@ -93,20 +78,6 @@ ifeq ($(HAVE_YSFX),true) | |||
STANDALONE_LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
STANDALONE_LINK_FLAGS += $(JUCE_AUDIO_BASICS_LIBS) | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
STANDALONE_LINK_FLAGS += $(JUCE_AUDIO_DEVICES_LIBS) | |||
endif | |||
STANDALONE_LINK_FLAGS += $(JUCE_AUDIO_PROCESSORS_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_CORE_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_DATA_STRUCTURES_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_EVENTS_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_GRAPHICS_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_GUI_BASICS_LIBS) | |||
STANDALONE_LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
endif | |||
ifeq ($(USING_RTAUDIO),true) | |||
STANDALONE_LINK_FLAGS += $(RTAUDIO_LIBS) | |||
STANDALONE_LINK_FLAGS += $(RTMIDI_LIBS) | |||
@@ -10,13 +10,6 @@ endif | |||
include $(CWD)/Makefile.mk | |||
# Workaround GCC bug | |||
ifeq ($(TESTBUILD),true) | |||
ifeq ($(USING_JUCE),true) | |||
BUILD_CXX_FLAGS += -Wno-undef | |||
endif | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
BINDIR := $(CWD)/../bin | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2023 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
/* TODO: | |||
* - complete processRack(): carefully add to input, sorted events | |||
@@ -100,10 +86,6 @@ uint CarlaEngine::getDriverCount() | |||
++count; | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
count += getJuceApiCount(); | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
count += getRtAudioApiCount(); | |||
#endif | |||
@@ -131,15 +113,6 @@ const char* CarlaEngine::getDriverName(const uint index) | |||
} | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
if (const uint count = getJuceApiCount()) | |||
{ | |||
if (index2 < count) | |||
return getJuceApiName(index2); | |||
index2 -= count; | |||
} | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
if (const uint count = getRtAudioApiCount()) | |||
{ | |||
@@ -178,15 +151,6 @@ const char* const* CarlaEngine::getDriverDeviceNames(const uint index) | |||
} | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
if (const uint count = getJuceApiCount()) | |||
{ | |||
if (index2 < count) | |||
return getJuceApiDeviceNames(index2); | |||
index2 -= count; | |||
} | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
if (const uint count = getRtAudioApiCount()) | |||
{ | |||
@@ -228,15 +192,6 @@ const EngineDriverDeviceInfo* CarlaEngine::getDriverDeviceInfo(const uint index, | |||
} | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
if (const uint count = getJuceApiCount()) | |||
{ | |||
if (index2 < count) | |||
return getJuceDeviceInfo(index2, deviceName); | |||
index2 -= count; | |||
} | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
if (const uint count = getRtAudioApiCount()) | |||
{ | |||
@@ -280,15 +235,6 @@ bool CarlaEngine::showDriverDeviceControlPanel(const uint index, const char* con | |||
} | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
if (const uint count = getJuceApiCount()) | |||
{ | |||
if (index2 < count) | |||
return showJuceDeviceControlPanel(index2, deviceName); | |||
index2 -= count; | |||
} | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
if (const uint count = getRtAudioApiCount()) | |||
{ | |||
@@ -324,30 +270,6 @@ CarlaEngine* CarlaEngine::newDriverByName(const char* const driverName) | |||
return newDummy(); | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
// ------------------------------------------------------------------- | |||
// linux | |||
if (std::strcmp(driverName, "ALSA") == 0) | |||
return newJuce(AUDIO_API_ALSA); | |||
// ------------------------------------------------------------------- | |||
// macos | |||
if (std::strcmp(driverName, "CoreAudio") == 0) | |||
return newJuce(AUDIO_API_COREAUDIO); | |||
// ------------------------------------------------------------------- | |||
// windows | |||
if (std::strcmp(driverName, "ASIO") == 0) | |||
return newJuce(AUDIO_API_ASIO); | |||
if (std::strcmp(driverName, "DirectSound") == 0) | |||
return newJuce(AUDIO_API_DIRECTSOUND); | |||
if (std::strcmp(driverName, "WASAPI") == 0 || std::strcmp(driverName, "Windows Audio") == 0) | |||
return newJuce(AUDIO_API_WASAPI); | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
// ------------------------------------------------------------------- | |||
// common | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#include "CarlaEngineClient.hpp" | |||
#include "CarlaEngineInit.hpp" | |||
@@ -29,10 +15,6 @@ | |||
#include "jackey.h" | |||
#ifdef USING_JUCE | |||
# include "carla_juce/carla_juce.h" | |||
#endif | |||
#ifdef __SSE2_MATH__ | |||
# include <xmmintrin.h> | |||
#endif | |||
@@ -4535,10 +4517,6 @@ int jack_initialize(jack_client_t* const client, const char* const load_init) | |||
else | |||
mode = ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS; | |||
#ifdef USING_JUCE | |||
CarlaJUCE::initialiseJuce_GUI(); | |||
#endif | |||
CarlaEngineJack* const engine = new CarlaEngineJack(); | |||
engine->setOption(ENGINE_OPTION_FORCE_STEREO, 1, nullptr); | |||
@@ -4564,9 +4542,7 @@ int jack_initialize(jack_client_t* const client, const char* const load_init) | |||
return 0; | |||
delete engine; | |||
#ifdef USING_JUCE | |||
CarlaJUCE::shutdownJuce_GUI(); | |||
#endif | |||
return 1; | |||
} | |||
@@ -4582,10 +4558,6 @@ void jack_finish(void *arg) | |||
engine->removeAllPlugins(); | |||
engine->close(); | |||
delete engine; | |||
#ifdef USING_JUCE | |||
CarlaJUCE::shutdownJuce_GUI(); | |||
#endif | |||
} | |||
// ----------------------------------------------------------------------- | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2023 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#include "CarlaDefines.h" | |||
@@ -49,10 +35,6 @@ | |||
# include <direct.h> | |||
#endif | |||
#ifdef USING_JUCE | |||
# include "carla_juce/carla_juce.h" | |||
#endif | |||
using water::File; | |||
using water::MemoryOutputStream; | |||
using water::String; | |||
@@ -111,13 +93,6 @@ public: | |||
const uint32_t cvIns = 0, const uint32_t cvOuts = 0) | |||
: CarlaEngine(), | |||
pHost(host), | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
// if not running inside Carla, we will have to run event loop ourselves | |||
kNeedsJuceEvents(host->dispatcher(pHost->handle, | |||
NATIVE_HOST_OPCODE_INTERNAL_PLUGIN, 0, 0, nullptr, 0.0f) == 0), | |||
fJuceMsgMgr(), | |||
fJuceMsgMutex(), | |||
#endif | |||
kIsPatchbay(isPatchbay), | |||
kHasMidiIn(withMidiIn), | |||
kHasMidiOut(withMidiOut), | |||
@@ -136,11 +111,6 @@ public: | |||
carla_zeroFloats(fParameters, kNumInParams+kNumOutParams); | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
if (kNeedsJuceEvents) | |||
fJuceMsgMgr.incRef(); | |||
#endif | |||
pData->bufferSize = pHost->get_buffer_size(pHost->handle); | |||
pData->sampleRate = pHost->get_sample_rate(pHost->handle); | |||
pData->initTime(nullptr); | |||
@@ -198,21 +168,11 @@ public: | |||
pData->aboutToClose = true; | |||
fIsRunning = false; | |||
{ | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
const ScopedJuceMessageThreadRunner sjmtr(*this, true); | |||
#endif | |||
removeAllPlugins(); | |||
//runPendingRtEvents(); | |||
close(); | |||
pData->graph.destroy(); | |||
} | |||
removeAllPlugins(); | |||
//runPendingRtEvents(); | |||
close(); | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
if (kNeedsJuceEvents) | |||
fJuceMsgMgr.decRef(); | |||
#endif | |||
pData->graph.destroy(); | |||
carla_debug("CarlaEngineNative::~CarlaEngineNative() - END"); | |||
} | |||
@@ -1353,13 +1313,6 @@ protected: | |||
void uiIdle() | |||
{ | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
const ScopedJuceMessageThreadRunner sjmtr(*this, false); | |||
if (kNeedsJuceEvents && ! sjmtr.wasLocked) | |||
return; | |||
#endif | |||
for (uint i=0; i < pData->curPluginCount; ++i) | |||
{ | |||
if (const CarlaPluginPtr plugin = pData->plugins[i].plugin) | |||
@@ -1547,10 +1500,6 @@ protected: | |||
void setState(const char* const data) | |||
{ | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
const ScopedJuceMessageThreadRunner sjmtr(*this, true); | |||
#endif | |||
// remove all plugins from UI side | |||
for (uint i=0, count=pData->curPluginCount; i < count; ++i) | |||
CarlaEngine::callback(true, true, ENGINE_CALLBACK_PLUGIN_REMOVED, count-i-1, 0, 0, 0, 0.0f, nullptr); | |||
@@ -1760,36 +1709,6 @@ public: | |||
private: | |||
const NativeHostDescriptor* const pHost; | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
const bool kNeedsJuceEvents; | |||
const CarlaJUCE::ReferenceCountedJuceMessageMessager fJuceMsgMgr; | |||
CarlaMutex fJuceMsgMutex; | |||
struct ScopedJuceMessageThreadRunner { | |||
const CarlaMutexTryLocker cmtl; | |||
const bool wasLocked; | |||
ScopedJuceMessageThreadRunner(CarlaEngineNative& self, const bool forceLock) noexcept | |||
: cmtl(self.fJuceMsgMutex, forceLock), | |||
wasLocked(cmtl.wasLocked()) | |||
{ | |||
if (! self.kNeedsJuceEvents) | |||
return; | |||
if (! wasLocked) | |||
return; | |||
CarlaJUCE::setMessageManagerForThisThread(); | |||
} | |||
~ScopedJuceMessageThreadRunner() | |||
{ | |||
CarlaJUCE::dispatchMessageManagerMessages(); | |||
} | |||
CARLA_DECLARE_NON_COPYABLE(ScopedJuceMessageThreadRunner) | |||
}; | |||
#endif | |||
const bool kIsPatchbay; // rack if false | |||
const bool kHasMidiIn; | |||
const bool kHasMidiOut; | |||
@@ -3122,15 +3041,6 @@ namespace EngineInit { | |||
CarlaEngine* newJack() { return nullptr; } | |||
#endif | |||
#ifdef USING_JUCE_AUDIO_DEVICES | |||
CarlaEngine* newJuce(const AudioApi) { return nullptr; } | |||
uint getJuceApiCount() { return 0; } | |||
const char* getJuceApiName(const uint) { return nullptr; } | |||
const char* const* getJuceApiDeviceNames(const uint) { return nullptr; } | |||
const EngineDriverDeviceInfo* getJuceDeviceInfo(const uint, const char* const) { return nullptr; } | |||
bool showJuceDeviceControlPanel(const uint, const char* const) { return false; } | |||
#endif | |||
#ifdef USING_RTAUDIO | |||
CarlaEngine* newRtAudio(const AudioApi) { return nullptr; } | |||
uint getRtAudioApiCount() { return 0; } | |||
@@ -290,7 +290,10 @@ public: | |||
} | |||
RtAudio::StreamOptions rtOptions; | |||
rtOptions.flags = RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_SCHEDULE_REALTIME; | |||
rtOptions.flags = RTAUDIO_SCHEDULE_REALTIME; | |||
#ifndef CARLA_OS_MAC | |||
rtOptions.flags |= RTAUDIO_MINIMIZE_LATENCY | |||
#endif | |||
rtOptions.numberOfBuffers = pData->options.audioTripleBuffer ? 3 : 2; | |||
rtOptions.streamName = clientName; | |||
rtOptions.priority = 85; | |||
@@ -7,13 +7,6 @@ | |||
CWD=../.. | |||
include ../Makefile.mk | |||
# Workaround GCC bug | |||
ifeq ($(TESTBUILD),true) | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
BUILD_CXX_FLAGS += -Wno-undef | |||
endif | |||
endif | |||
BUILD_CXX_FLAGS += $(MAGIC_FLAGS) | |||
ifneq ($(HAIKU),true) | |||
@@ -48,11 +41,6 @@ endif | |||
OBJSa = $(OBJS) \ | |||
$(OBJDIR)/CarlaEngineNative.cpp.o | |||
ifeq ($(USING_JUCE_AUDIO_DEVICES),true) | |||
OBJSa += \ | |||
$(OBJDIR)/CarlaEngineJuce.cpp.o | |||
endif | |||
ifeq ($(USING_RTAUDIO),true) | |||
OBJSa += \ | |||
$(OBJDIR)/CarlaEngineRtAudio.cpp.o | |||
@@ -132,13 +120,6 @@ $(OBJDIR)/CarlaEngineJack.cpp.o: CarlaEngineJack.cpp | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(JACK_FLAGS) -c -o $@ | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
$(OBJDIR)/CarlaEngineJuce.cpp.o: CarlaEngineJuce.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -std=gnu++14 -c -o $@ | |||
endif | |||
$(OBJDIR)/%.cpp.o: %.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla AU Plugin | |||
* Copyright (C) 2014-2020 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#include "CarlaPlugin.hpp" | |||
#include "CarlaEngine.hpp" | |||
@@ -28,12 +14,8 @@ CarlaPluginPtr CarlaPlugin::newAU(const Initializer& init) | |||
carla_debug("CarlaPlugin::newAU({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})", | |||
init.engine, init.filename, init.name, init.label, init.uniqueId); | |||
#if defined(CARLA_OS_MAC) | |||
return newJuce(init, "AU"); | |||
#else | |||
init.engine->setLastError("AU support not available"); | |||
return nullptr; | |||
#endif | |||
} | |||
// ------------------------------------------------------------------------------------------------------------------- | |||
@@ -1,27 +1,8 @@ | |||
/* | |||
* Carla VST2 Plugin | |||
* Copyright (C) 2011-2022 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#include "CarlaPluginInternal.hpp" | |||
#include "CarlaEngine.hpp" | |||
#include "AppConfig.h" | |||
#if defined(USING_JUCE) && JUCE_PLUGINHOST_VST | |||
# define USE_JUCE_FOR_VST2 | |||
#endif | |||
#include "CarlaBackendUtils.hpp" | |||
#include "CarlaMathUtils.hpp" | |||
@@ -2970,11 +2951,6 @@ CarlaPluginPtr CarlaPlugin::newVST2(const Initializer& init) | |||
carla_debug("CarlaPlugin::newVST2({%p, \"%s\", \"%s\", " P_INT64 "})", | |||
init.engine, init.filename, init.name, init.uniqueId); | |||
#ifdef USE_JUCE_FOR_VST2 | |||
if (std::getenv("CARLA_DO_NOT_USE_JUCE_FOR_VST2") == nullptr) | |||
return newJuce(init, "VST2"); | |||
#endif | |||
std::shared_ptr<CarlaPluginVST2> plugin(new CarlaPluginVST2(init.engine, init.id)); | |||
if (! plugin->init(plugin, init.filename, init.name, init.uniqueId, init.options)) | |||
@@ -1,19 +1,5 @@ | |||
/* | |||
* Carla VST3 Plugin | |||
* Copyright (C) 2014-2023 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. | |||
*/ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
/* TODO list | |||
* noexcept safe calls | |||
@@ -21,11 +7,6 @@ | |||
*/ | |||
#include "CarlaPluginInternal.hpp" | |||
#include "CarlaEngine.hpp" | |||
#include "AppConfig.h" | |||
#if defined(USING_JUCE) && JUCE_PLUGINHOST_VST3 | |||
# define USE_JUCE_FOR_VST3 | |||
#endif | |||
#include "CarlaBackendUtils.hpp" | |||
#include "CarlaVst3Utils.hpp" | |||
@@ -4322,11 +4303,6 @@ CarlaPluginPtr CarlaPlugin::newVST3(const Initializer& init) | |||
carla_debug("CarlaPlugin::newVST3({%p, \"%s\", \"%s\", \"%s\"})", | |||
init.engine, init.filename, init.name, init.label); | |||
#ifdef USE_JUCE_FOR_VST3 | |||
if (std::getenv("CARLA_DO_NOT_USE_JUCE_FOR_VST3") == nullptr) | |||
return newJuce(init, "VST3"); | |||
#endif | |||
std::shared_ptr<CarlaPluginVST3> plugin(new CarlaPluginVST3(init.engine, init.id)); | |||
if (! plugin->init(plugin, init.filename, init.name, init.label, init.options)) | |||
@@ -7,13 +7,6 @@ | |||
CWD=../.. | |||
include ../Makefile.mk | |||
# Workaround GCC bug | |||
ifeq ($(TESTBUILD),true) | |||
ifeq ($(USING_JUCE),true) | |||
BUILD_CXX_FLAGS += -Wno-undef | |||
endif | |||
endif | |||
BUILD_CXX_FLAGS += $(MAGIC_FLAGS) | |||
ifneq ($(HAIKU),true) | |||
@@ -35,7 +28,6 @@ OBJS = \ | |||
$(OBJDIR)/CarlaPluginVST3.cpp.o \ | |||
$(OBJDIR)/CarlaPluginAU.cpp.o \ | |||
$(OBJDIR)/CarlaPluginJSFX.cpp.o \ | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o \ | |||
$(OBJDIR)/CarlaPluginFluidSynth.cpp.o \ | |||
$(OBJDIR)/CarlaPluginSFZero.cpp.o | |||
@@ -99,20 +91,6 @@ $(OBJDIR)/CarlaPluginCLAP.cpp.o: CarlaPluginCLAP.cpp | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
ifeq ($(MACOS),true) | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o: CarlaPluginJuce.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -std=gnu++14 -c -o $@ | |||
else | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o: CarlaPluginJuce.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -std=gnu++14 -c -o $@ | |||
endif | |||
endif | |||
$(OBJDIR)/%.cpp.o: %.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
@@ -46,18 +46,10 @@ const char* carla_get_complete_license_text() | |||
"<li>LADSPA plugin support</li>" | |||
"<li>DSSI plugin support</li>" | |||
"<li>LV2 plugin support</li>" | |||
#if defined(USING_JUCE) && JUCE_PLUGINHOST_VST | |||
"<li>VST2 plugin support (using JUCE)</li>" | |||
#else | |||
"<li>VST2 plugin support (using VeSTige header by Javier Serrano Polo)</li>" | |||
#endif | |||
#if defined(USING_JUCE) && JUCE_PLUGINHOST_VST3 | |||
"<li>VST3 plugin support (using JUCE)</li>" | |||
#else | |||
"<li>VST3 plugin support (using Travesty header files)</li>" | |||
#endif | |||
#if defined(USING_JUCE) && JUCE_PLUGINHOST_AU | |||
"<li>AU plugin support (using JUCE)</li>" | |||
#ifdef CARLA_OS_MAC | |||
"<li>AU plugin support (discovery only)</li>" | |||
#endif | |||
#ifdef HAVE_YSFX | |||
"<li>JSFX plugin support (using ysfx)</li>" | |||
@@ -104,17 +96,6 @@ const char* carla_get_complete_license_text() | |||
return retText; | |||
} | |||
const char* carla_get_juce_version() | |||
{ | |||
carla_debug("carla_get_juce_version()"); | |||
#ifdef USING_JUCE | |||
return "JUCE v7.0.1"; | |||
#else | |||
return "Unknown"; | |||
#endif | |||
} | |||
const char* const* carla_get_supported_file_extensions() | |||
{ | |||
carla_debug("carla_get_supported_file_extensions()"); | |||
@@ -203,12 +184,6 @@ const char* const* carla_get_supported_features() | |||
#ifdef HAVE_YSFX | |||
"jsfx", | |||
#endif | |||
#ifdef USING_JUCE | |||
"juce", | |||
#if defined(CARLA_OS_MAC) | |||
"au", | |||
#endif | |||
#endif | |||
nullptr | |||
}; | |||
@@ -1,33 +0,0 @@ | |||
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||
// SPDX-License-Identifier: GPL-2.0-or-later | |||
#include "CarlaUtils.h" | |||
#ifdef USING_JUCE | |||
# include "carla_juce/carla_juce.h" | |||
#endif | |||
// ------------------------------------------------------------------------------------------------------------------- | |||
void carla_juce_init() | |||
{ | |||
#ifdef USING_JUCE | |||
CarlaJUCE::initialiseJuce_GUI(); | |||
#endif | |||
} | |||
void carla_juce_idle() | |||
{ | |||
#ifdef USING_JUCE | |||
CarlaJUCE::idleJuce_GUI(); | |||
#endif | |||
} | |||
void carla_juce_cleanup() | |||
{ | |||
#ifdef USING_JUCE | |||
CarlaJUCE::shutdownJuce_GUI(); | |||
#endif | |||
} | |||
// ------------------------------------------------------------------------------------------------------------------- |
@@ -13,16 +13,12 @@ BUILD_CXX_FLAGS += $(FLUIDSYNTH_FLAGS) | |||
BUILD_CXX_FLAGS += $(MAGIC_LIBS) | |||
BUILD_CXX_FLAGS += $(YSFX_FLAGS) | |||
# FIXME remove this after getting rid of juce | |||
BUILD_CXX_FLAGS += -UUSING_JUCE | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
OBJS = \ | |||
$(OBJDIR)/CachedPlugins.cpp.o \ | |||
$(OBJDIR)/CarlaUtils.cpp.o \ | |||
$(OBJDIR)/Information.cpp.o \ | |||
$(OBJDIR)/JUCE.cpp.o \ | |||
$(OBJDIR)/PipeClient.cpp.o \ | |||
$(OBJDIR)/PluginDiscovery.cpp.o \ | |||
$(OBJDIR)/System.cpp.o \ | |||
@@ -1,6 +1,6 @@ | |||
/* | |||
* Carla LV2 Single Plugin | |||
* Copyright (C) 2017-2022 Filipe Coelho <falktx@falktx.com> | |||
* Copyright (C) 2017-2024 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 | |||
@@ -29,10 +29,6 @@ | |||
#include "water/files/File.h" | |||
#ifdef USING_JUCE | |||
# include "carla_juce/carla_juce.h" | |||
#endif | |||
template<> | |||
void Lv2PluginBaseClass<CARLA_BACKEND_NAMESPACE::EngineTimeInfo>::clearTimeData() noexcept | |||
{ | |||
@@ -53,9 +49,6 @@ public: | |||
const LV2_Feature* const* const features) | |||
: Lv2PluginBaseClass<EngineTimeInfo>(sampleRate, features), | |||
fPlugin(nullptr) | |||
#ifdef USING_JUCE | |||
, fJuceInitialiser() | |||
#endif | |||
{ | |||
CARLA_SAFE_ASSERT_RETURN(pData->curPluginCount == 0,) | |||
CARLA_SAFE_ASSERT_RETURN(pData->plugins == nullptr,); | |||
@@ -490,10 +483,6 @@ protected: | |||
private: | |||
CarlaPluginPtr fPlugin; | |||
#ifdef USING_JUCE | |||
CarlaJUCE::ScopedJuceInitialiser_GUI fJuceInitialiser; | |||
#endif | |||
void updateParameterOutputs() noexcept | |||
{ | |||
float value; | |||
@@ -8,15 +8,6 @@ CWD=.. | |||
MODULENAME=carla-bridge | |||
include $(CWD)/Makefile.mk | |||
# Workaround GCC bug | |||
ifeq ($(TESTBUILD),true) | |||
ifeq ($(USING_JUCE),true) | |||
BUILD_CXX_FLAGS += -Wno-undef | |||
# FIXME | |||
BUILD_CXX_FLAGS += -Wno-deprecated-declarations | |||
endif | |||
endif | |||
ifneq ($(HAIKU),true) | |||
ifneq ($(WASM),true) | |||
BUILD_CXX_FLAGS += -pthread | |||
@@ -41,7 +32,6 @@ BUILD_CXX_FLAGS += -DBUILD_BRIDGE -I. -I$(CWD) -I$(CWD)/backend -I$(CWD)/include | |||
BUILD_CXX_FLAGS += -I$(CWD)/backend/engine -I$(CWD)/backend/plugin | |||
BUILD_CXX_FLAGS += -UHAVE_SDL | |||
BUILD_CXX_FLAGS += -UUSING_JUCE_AUDIO_DEVICES | |||
BUILD_CXX_FLAGS += -UUSING_RTAUDIO | |||
32BIT_FLAGS += -DBUILD_BRIDGE_ALTERNATIVE_ARCH | |||
@@ -92,27 +82,6 @@ LIBS_native += $(MODULEDIR)/ysfx.a | |||
LINK_FLAGS += $(YSFX_GRAPHICS_LIBS) | |||
endif | |||
ifeq ($(USING_JUCE),true) | |||
LIBS_native += $(MODULEDIR)/carla_juce.a | |||
LIBS_native += $(MODULEDIR)/juce_audio_basics.a | |||
LIBS_native += $(MODULEDIR)/juce_audio_processors.a | |||
LIBS_native += $(MODULEDIR)/juce_core.a | |||
LIBS_native += $(MODULEDIR)/juce_data_structures.a | |||
LIBS_native += $(MODULEDIR)/juce_events.a | |||
LIBS_native += $(MODULEDIR)/juce_graphics.a | |||
LIBS_native += $(MODULEDIR)/juce_gui_basics.a | |||
LIBS_native += $(MODULEDIR)/juce_gui_extra.a | |||
LINK_FLAGS += $(JUCE_AUDIO_BASICS_LIBS) | |||
LINK_FLAGS += $(JUCE_AUDIO_PROCESSORS_LIBS) | |||
LINK_FLAGS += $(JUCE_CORE_LIBS) | |||
LINK_FLAGS += $(JUCE_DATA_STRUCTURES_LIBS) | |||
LINK_FLAGS += $(JUCE_EVENTS_LIBS) | |||
LINK_FLAGS += $(JUCE_GRAPHICS_LIBS) | |||
LINK_FLAGS += $(JUCE_GUI_BASICS_LIBS) | |||
LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
endif | |||
ifeq ($(JACKBRIDGE_DIRECT),true) | |||
LINK_FLAGS += $(JACK_LIBS) | |||
endif | |||
@@ -179,7 +148,6 @@ OBJS_native = \ | |||
$(OBJDIR)/CarlaPluginVST3.cpp.o \ | |||
$(OBJDIR)/CarlaPluginAU.cpp.o \ | |||
$(OBJDIR)/CarlaPluginJSFX.cpp.o \ | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o \ | |||
$(OBJDIR)/CarlaPluginFluidSynth.cpp.o \ | |||
$(OBJDIR)/CarlaPluginSFZero.cpp.o \ | |||
$(OBJDIR)/CarlaStandalone.cpp.o | |||
@@ -307,13 +275,9 @@ $(OBJDIR)/CarlaEng%.cpp.o: $(CWD)/backend/engine/CarlaEng%.cpp | |||
$(OBJDIR)/CarlaPluginJSFX.cpp.o: BUILD_CXX_FLAGS += $(YSFX_FLAGS) | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o: BUILD_CXX_FLAGS += -std=gnu++14 | |||
ifeq ($(MACOS),true) | |||
$(OBJDIR)/CarlaPluginCLAP.cpp.o: BUILD_CXX_FLAGS += -ObjC++ | |||
$(OBJDIR)/CarlaPluginJuce.cpp.o: BUILD_CXX_FLAGS += -ObjC++ | |||
$(OBJDIR)/CarlaPluginVST2.cpp.o: BUILD_CXX_FLAGS += -ObjC++ | |||
$(OBJDIR)/CarlaPluginVST3.cpp.o: BUILD_CXX_FLAGS += -ObjC++ | |||
@@ -3,7 +3,7 @@ Manager=KDevGenericManager | |||
Name=Carla | |||
[Filters] | |||
Excludes=*/.*,*/*~,*/*.pyc,*/ui_*.py,*/__pycache__/,external,juce_* | |||
Excludes=*/.*,*/*~,*/*.pyc,*/ui_*.py,*/__pycache__/,external | |||
[Project] | |||
VersionControlSupport=kdevgit |
@@ -55,9 +55,6 @@ struct PluginListDialog; | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
CARLA_PLUGIN_EXPORT void | |||
carla_frontend_createAndExecAboutJuceDialog(void* parent); | |||
CARLA_PLUGIN_EXPORT const JackAppDialogResults* | |||
carla_frontend_createAndExecJackAppDialog(void* parent, const char* projectFilename); | |||
@@ -43,6 +43,10 @@ else ifeq ($(HAVE_QT5PKG),true) | |||
QT5_PREFIX = $(shell $(PKG_CONFIG) --variable=prefix Qt5OpenGLExtensions) | |||
BUILD_CXX_FLAGS += -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -I $(QT5_PREFIX)/include/qt5 | |||
QT5_LINK_FLAGS += -Wl,-rpath,$(QT5_PREFIX)/lib -F $(QT5_PREFIX)/lib -framework QtCore -framework QtGui -framework QtWidgets | |||
else ifeq ($(HAVE_QT5BREW),true) | |||
QT5_PREFIX = /usr/local/opt/qt5 | |||
BUILD_CXX_FLAGS += -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -I $(QT5_PREFIX)/include | |||
QT5_LINK_FLAGS += -Wl,-rpath,$(QT5_PREFIX)/lib -F $(QT5_PREFIX)/lib -framework QtCore -framework QtGui -framework QtWidgets | |||
else | |||
$(error Trying to build frontend without Qt5, cannot continue) | |||
endif | |||
@@ -71,7 +75,6 @@ QMs = $(patsubst %,translations/carla_%.qm,$(I18N_LANGUAGES)) | |||
CPP_FILES = \ | |||
carla_frontend.cpp \ | |||
dialogs/aboutjucedialog.cpp \ | |||
dialogs/jackappdialog.cpp \ | |||
pluginlist/pluginlistdialog.cpp | |||
@@ -217,7 +220,6 @@ $(OBJDIR)/%.cpp.o: %.cpp $(UIs) | |||
clean: | |||
rm -rf $(BINDIR)/libcarla_frontend$(LIB_EXT) $(UIs) $(RES) $(QMs) __pycache__ *.pyc | |||
# old files | |||
rm -f ui_carla_about_juce.py | |||
rm -f ui_carla_add_jack.py | |||
rm -f ui_carla_database.py | |||
rm -f ui_carla_refresh.py | |||
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# Carla Backend utils | |||
# Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2011-2024 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 | |||
@@ -91,9 +91,6 @@ class CarlaFrontendLib(): | |||
def __init__(self, filename): | |||
self.lib = cdll.LoadLibrary(filename) | |||
self.lib.carla_frontend_createAndExecAboutJuceDialog.argtypes = (c_void_p,) | |||
self.lib.carla_frontend_createAndExecAboutJuceDialog.restype = None | |||
self.lib.carla_frontend_createAndExecJackAppDialog.argtypes = (c_void_p, c_char_p) | |||
self.lib.carla_frontend_createAndExecJackAppDialog.restype = POINTER(JackApplicationDialogResults) | |||
@@ -111,9 +108,6 @@ class CarlaFrontendLib(): | |||
# -------------------------------------------------------------------------------------------------------- | |||
def createAndExecAboutJuceDialog(self, parent): | |||
self.lib.carla_frontend_createAndExecAboutJuceDialog(unwrapinstance(parent)) | |||
def createAndExecJackAppDialog(self, parent, projectFilename): | |||
return structToDictOrNull(self.lib.carla_frontend_createAndExecJackAppDialog(unwrapinstance(parent), | |||
projectFilename.encode("utf-8"))) | |||
@@ -430,7 +430,6 @@ class HostWindow(QMainWindow): | |||
self.ui.act_file_quit.setMenuRole(QAction.QuitRole) | |||
self.ui.act_settings_configure.setMenuRole(QAction.PreferencesRole) | |||
self.ui.act_help_about.setMenuRole(QAction.AboutRole) | |||
self.ui.act_help_about_juce.setMenuRole(QAction.ApplicationSpecificRole) | |||
self.ui.act_help_about_qt.setMenuRole(QAction.AboutQtRole) | |||
self.ui.menu_Settings.setTitle("Panels") | |||
@@ -520,7 +519,6 @@ class HostWindow(QMainWindow): | |||
self.ui.act_settings_configure.triggered.connect(self.slot_configureCarla) | |||
self.ui.act_help_about.triggered.connect(self.slot_aboutCarla) | |||
self.ui.act_help_about_juce.triggered.connect(self.slot_aboutJuce) | |||
self.ui.act_help_about_qt.triggered.connect(self.slot_aboutQt) | |||
self.ui.cb_disk.currentIndexChanged.connect(self.slot_diskFolderChanged) | |||
@@ -624,10 +622,6 @@ class HostWindow(QMainWindow): | |||
self.ui.cb_transport_link.setEnabled(False) | |||
self.ui.cb_transport_link.setVisible(False) | |||
if "juce" not in features: | |||
self.ui.act_help_about_juce.setEnabled(False) | |||
self.ui.act_help_about_juce.setVisible(False) | |||
# Plugin needs to have timers always running so it receives messages | |||
if self.host.isPlugin or self.host.isRemote: | |||
self.startTimers() | |||
@@ -2182,10 +2176,6 @@ class HostWindow(QMainWindow): | |||
def slot_aboutCarla(self): | |||
CarlaAboutW(self.fParentOrSelf, self.host).exec_() | |||
@pyqtSlot() | |||
def slot_aboutJuce(self): | |||
gCarla.felib.createAndExecAboutJuceDialog(self.fParentOrSelf) | |||
@pyqtSlot() | |||
def slot_aboutQt(self): | |||
QApplication.instance().aboutQt() | |||
@@ -202,18 +202,11 @@ class CarlaAboutW(QDialog): | |||
"<li>http://ll-plugins.nongnu.org/lv2/ext/miditype</li>" | |||
"</ul>")) | |||
usingJuce = "juce" in gCarla.utils.get_supported_features() | |||
if usingJuce and (MACOS or WINDOWS): | |||
self.ui.l_vst2.setText(self.tr("Using JUCE host")) | |||
else: | |||
self.ui.l_vst2.setText(self.tr("About 85% complete (missing vst bank/presets and some minor stuff)")) | |||
if usingJuce: | |||
self.ui.l_vst3.setText(self.tr("Using JUCE host")) | |||
self.ui.l_vst2.setText(self.tr("About 85% complete (missing vst bank/presets and some minor stuff)")) | |||
self.ui.l_vst3.setText(self.tr("About 66% complete")) | |||
if MACOS: | |||
self.ui.l_au.setText(self.tr("Using JUCE host")) | |||
self.ui.l_au.setText(self.tr("About 20% complete")) | |||
else: | |||
self.ui.line_vst3.hide() | |||
self.ui.l_au.hide() | |||
@@ -1,97 +0,0 @@ | |||
/* | |||
* Carla plugin host | |||
* Copyright (C) 2011-2023 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. | |||
*/ | |||
#include "aboutjucedialog.hpp" | |||
#ifdef __clang__ | |||
# pragma clang diagnostic push | |||
# pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" | |||
# pragma clang diagnostic ignored "-Wdeprecated-register" | |||
#elif defined(__GNUC__) && __GNUC__ >= 8 | |||
# pragma GCC diagnostic push | |||
# pragma GCC diagnostic ignored "-Wclass-memaccess" | |||
# pragma GCC diagnostic ignored "-Wdeprecated-copy" | |||
#endif | |||
#include "ui_aboutjucedialog.h" | |||
#ifdef __clang__ | |||
# pragma clang diagnostic pop | |||
#elif defined(__GNUC__) && __GNUC__ >= 8 | |||
# pragma GCC diagnostic pop | |||
#endif | |||
#include "CarlaFrontend.h" | |||
#include "CarlaUtils.h" | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// Jack Application Dialog | |||
struct AboutJuceDialog::Self { | |||
Ui_AboutJuceDialog ui; | |||
Self() {} | |||
static Self& create() | |||
{ | |||
Self* const self = new Self(); | |||
return *self; | |||
} | |||
}; | |||
AboutJuceDialog::AboutJuceDialog(QWidget* const parent) | |||
: QDialog(parent), | |||
self(Self::create()) | |||
{ | |||
self.ui.setupUi(this); | |||
// ------------------------------------------------------------------------------------------------------------- | |||
// UI setup | |||
self.ui.l_text2->setText(tr("This program uses JUCE version") + " " + carla_get_juce_version() + "."); | |||
adjustSize(); | |||
setFixedSize(size()); | |||
Qt::WindowFlags flags = windowFlags(); | |||
flags &= ~Qt::WindowContextHelpButtonHint; | |||
#ifdef CARLA_OS_WIN | |||
flags |= Qt::MSWindowsFixedSizeDialogHint; | |||
#endif | |||
setWindowFlags(flags); | |||
#ifdef CARLA_OS_MAC | |||
if (parent != nullptr) | |||
setWindowModality(Qt::WindowModal); | |||
#endif | |||
} | |||
AboutJuceDialog::~AboutJuceDialog() | |||
{ | |||
delete &self; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
void carla_frontend_createAndExecAboutJuceDialog(void* const parent) | |||
{ | |||
AboutJuceDialog(reinterpret_cast<QWidget*>(parent)).exec(); | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- |
@@ -1,55 +0,0 @@ | |||
/* | |||
* Carla plugin host | |||
* Copyright (C) 2011-2022 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. | |||
*/ | |||
#pragma once | |||
#ifdef __clang__ | |||
# pragma clang diagnostic push | |||
# pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" | |||
# pragma clang diagnostic ignored "-Wdeprecated-register" | |||
#elif defined(__GNUC__) && __GNUC__ >= 8 | |||
# pragma GCC diagnostic push | |||
# pragma GCC diagnostic ignored "-Wclass-memaccess" | |||
# pragma GCC diagnostic ignored "-Wdeprecated-copy" | |||
#endif | |||
#include <QtWidgets/QDialog> | |||
#ifdef __clang__ | |||
# pragma clang diagnostic pop | |||
#elif defined(__GNUC__) && __GNUC__ >= 8 | |||
# pragma GCC diagnostic pop | |||
#endif | |||
#include "CarlaDefines.h" | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// About JUCE dialog | |||
class AboutJuceDialog : public QDialog | |||
{ | |||
struct Self; | |||
Self& self; | |||
// ---------------------------------------------------------------------------------------------------------------- | |||
public: | |||
explicit AboutJuceDialog(QWidget* parent); | |||
~AboutJuceDialog() override; | |||
}; | |||
// -------------------------------------------------------------------------------------------------------------------- |
@@ -1,77 +0,0 @@ | |||
#!/usr/bin/env python3 | |||
# -*- coding: utf-8 -*- | |||
# Carla plugin host | |||
# Copyright (C) 2011-2022 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. | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Global) | |||
from PyQt5.QtCore import Qt | |||
from PyQt5.QtWidgets import QDialog | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Carla) | |||
from common import MACOS, WINDOWS | |||
from carla_shared import gCarla | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Local) | |||
from aboutjucedialog_ui import Ui_AboutJuceDialog | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# About JUCE dialog | |||
class AboutJuceDialog(QDialog): | |||
def __init__(self, parent): | |||
QDialog.__init__(self, parent) | |||
self.ui = Ui_AboutJuceDialog() | |||
self.ui.setupUi(self) | |||
self.ui.l_text2.setText(self.tr("This program uses JUCE version %s." % gCarla.utils.get_juce_version())) | |||
self.adjustSize() | |||
self.setFixedSize(self.size()) | |||
flags = self.windowFlags() | |||
flags &= ~Qt.WindowContextHelpButtonHint | |||
if WINDOWS: | |||
flags |= Qt.MSWindowsFixedSizeDialogHint | |||
self.setWindowFlags(flags) | |||
if MACOS: | |||
self.setWindowModality(Qt.WindowModal) | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Testing | |||
if __name__ == '__main__': | |||
import sys | |||
# pylint: disable=ungrouped-imports | |||
from PyQt5.QtWidgets import QApplication | |||
# pylint: enable=ungrouped-imports | |||
# from carla_utils import CarlaUtils | |||
# gCarla.utils = CarlaUtils(os.path.dirname(__file__) + "/../../../bin/libcarla_utils.dylib") | |||
_app = QApplication(sys.argv) | |||
_gui = AboutJuceDialog(None) | |||
_gui.exec_() | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -1,187 +0,0 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<ui version="4.0"> | |||
<class>AboutJuceDialog</class> | |||
<widget class="QDialog" name="AboutJuceDialog"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>463</width> | |||
<height>244</height> | |||
</rect> | |||
</property> | |||
<property name="windowTitle"> | |||
<string>About JUCE</string> | |||
</property> | |||
<layout class="QGridLayout" name="gridLayout"> | |||
<item row="0" column="0"> | |||
<layout class="QVBoxLayout" name="verticalLayout"> | |||
<item> | |||
<widget class="QLabel" name="icon"> | |||
<property name="minimumSize"> | |||
<size> | |||
<width>48</width> | |||
<height>48</height> | |||
</size> | |||
</property> | |||
<property name="maximumSize"> | |||
<size> | |||
<width>48</width> | |||
<height>48</height> | |||
</size> | |||
</property> | |||
<property name="text"> | |||
<string/> | |||
</property> | |||
<property name="pixmap"> | |||
<pixmap resource="../resources.qrc">:/48x48/juce.png</pixmap> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<spacer name="verticalSpacer"> | |||
<property name="orientation"> | |||
<enum>Qt::Vertical</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>20</width> | |||
<height>40</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
</layout> | |||
</item> | |||
<item row="0" column="1"> | |||
<layout class="QVBoxLayout" name="verticalLayout_2"> | |||
<item> | |||
<widget class="QLabel" name="l_text1"> | |||
<property name="text"> | |||
<string><b>About JUCE</b></string> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<spacer name="verticalSpacer_3"> | |||
<property name="orientation"> | |||
<enum>Qt::Vertical</enum> | |||
</property> | |||
<property name="sizeType"> | |||
<enum>QSizePolicy::Fixed</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>10</width> | |||
<height>10</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
<item> | |||
<widget class="QLabel" name="l_text2"> | |||
<property name="text"> | |||
<string>This program uses JUCE version 3.x.x.</string> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<spacer name="verticalSpacer_4"> | |||
<property name="orientation"> | |||
<enum>Qt::Vertical</enum> | |||
</property> | |||
<property name="sizeType"> | |||
<enum>QSizePolicy::Fixed</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>10</width> | |||
<height>10</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
<item> | |||
<widget class="QLabel" name="l_text3"> | |||
<property name="text"> | |||
<string>JUCE is an open-source cross-platform C++ application framework for creating high quality desktop and mobile applications. | |||
The core JUCE modules (juce_audio_basics, juce_audio_devices, juce_core and juce_events) are permissively licensed under the terms of the ISC license. | |||
Other modules are covered by a GNU GPL 3.0 license. | |||
Copyright (C) 2022 Raw Material Software Limited.</string> | |||
</property> | |||
<property name="wordWrap"> | |||
<bool>true</bool> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<spacer name="verticalSpacer_2"> | |||
<property name="orientation"> | |||
<enum>Qt::Vertical</enum> | |||
</property> | |||
<property name="sizeType"> | |||
<enum>QSizePolicy::Expanding</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>0</width> | |||
<height>0</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
</layout> | |||
</item> | |||
<item row="1" column="0" colspan="2"> | |||
<widget class="QDialogButtonBox" name="buttonBox"> | |||
<property name="orientation"> | |||
<enum>Qt::Horizontal</enum> | |||
</property> | |||
<property name="standardButtons"> | |||
<set>QDialogButtonBox::Ok</set> | |||
</property> | |||
</widget> | |||
</item> | |||
</layout> | |||
</widget> | |||
<resources> | |||
<include location="../resources.qrc"/> | |||
<include location="../resources.qrc"/> | |||
</resources> | |||
<connections> | |||
<connection> | |||
<sender>buttonBox</sender> | |||
<signal>accepted()</signal> | |||
<receiver>AboutJuceDialog</receiver> | |||
<slot>accept()</slot> | |||
<hints> | |||
<hint type="sourcelabel"> | |||
<x>248</x> | |||
<y>254</y> | |||
</hint> | |||
<hint type="destinationlabel"> | |||
<x>157</x> | |||
<y>274</y> | |||
</hint> | |||
</hints> | |||
</connection> | |||
<connection> | |||
<sender>buttonBox</sender> | |||
<signal>rejected()</signal> | |||
<receiver>AboutJuceDialog</receiver> | |||
<slot>reject()</slot> | |||
<hints> | |||
<hint type="sourcelabel"> | |||
<x>316</x> | |||
<y>260</y> | |||
</hint> | |||
<hint type="destinationlabel"> | |||
<x>286</x> | |||
<y>274</y> | |||
</hint> | |||
</hints> | |||
</connection> | |||
</connections> | |||
</ui> |
@@ -1,495 +0,0 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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_APPCONFIG_H_INCLUDED | |||
#define CARLA_JUCE_APPCONFIG_H_INCLUDED | |||
#if defined(DEBUG) && defined(NDEBUG) | |||
# undef DEBUG | |||
#endif | |||
#define juce carlajuce | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// 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(__linux__) || defined(__linux) | |||
# define APPCONFIG_OS_LINUX | |||
#elif defined(__FreeBSD__) | |||
# define APPCONFIG_OS_FREEBSD | |||
#endif | |||
#if defined(APPCONFIG_OS_WIN32) || defined(APPCONFIG_OS_WIN64) | |||
# define APPCONFIG_OS_WIN | |||
#elif defined(APPCONFIG_OS_LINUX) || defined(APPCONFIG_OS_FREEBSD) || 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_processors 1 | |||
#define JUCE_MODULE_AVAILABLE_juce_core 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 | |||
// always disabled | |||
#define JUCE_MODULE_AVAILABLE_juce_audio_formats 0 | |||
#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(USING_JUCE_AUDIO_DEVICES) && !defined(BUILD_BRIDGE) | |||
# define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 | |||
#else | |||
# define JUCE_MODULE_AVAILABLE_juce_audio_devices 0 | |||
#endif | |||
// misc | |||
#define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | |||
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 | |||
#define JUCE_STANDALONE_APPLICATION 0 | |||
#define JUCE_REPORT_APP_USAGE 0 | |||
#define JUCE_DISPLAY_SPLASH_SCREEN 0 | |||
#define JUCE_USE_DARK_SPLASH_SCREEN 0 | |||
#define JUCE_STRING_UTF_TYPE 8 | |||
#define JUCE_USE_VFORK 1 | |||
#if defined(APPCONFIG_OS_LINUX) || defined(APPCONFIG_OS_FREEBSD) | |||
# define JUCE_DISABLE_NATIVE_FILECHOOSERS 1 | |||
# define JUCE_MODAL_LOOPS_PERMITTED 0 | |||
// # define JUCE_AUDIOPROCESSOR_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). | |||
*/ | |||
#if defined(APPCONFIG_OS_WIN) && !defined(BUILDING_CARLA_NOOPT) | |||
#define JUCE_WASAPI 1 | |||
#else | |||
#define JUCE_WASAPI 0 | |||
#endif | |||
/** 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). | |||
*/ | |||
#ifdef APPCONFIG_OS_LINUX | |||
#define JUCE_ALSA 1 | |||
#define JUCE_ALSA_MIDI_NAME "Carla" | |||
#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 defined(APPCONFIG_OS_LINUX) || defined(APPCONFIG_OS_FREEBSD) | |||
#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 0 | |||
/** 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 0 | |||
/** Config: JUCE_USE_MP3AUDIOFORMAT | |||
Enables the software-based MP3AudioFormat class. | |||
IMPORTANT DISCLAIMER: By choosing to enable the JUCE_USE_MP3AUDIOFORMAT flag and to compile | |||
this MP3 code into your software, you do so AT YOUR OWN RISK! By doing so, you are agreeing | |||
that Raw Material Software is in no way responsible for any patent, copyright, or other | |||
legal issues that you may suffer as a result. | |||
The code in juce_MP3AudioFormat.cpp is NOT guaranteed to be free from infringements of 3rd-party | |||
intellectual property. If you wish to use it, please seek your own independent advice about the | |||
legality of doing so. If you are not willing to accept full responsibility for the consequences | |||
of using this code, then do not enable this setting. | |||
*/ | |||
#define JUCE_USE_MP3AUDIOFORMAT 0 | |||
/** Config: JUCE_USE_LAME_AUDIO_FORMAT | |||
Enables the LameEncoderAudioFormat class. | |||
*/ | |||
#define JUCE_USE_LAME_AUDIO_FORMAT 0 | |||
/** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT | |||
Enables the Windows Media SDK codecs. | |||
*/ | |||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// juce_audio_processors | |||
#define JUCE_VST_FALLBACK_HOST_NAME "Carla" | |||
//============================================================================= | |||
/** 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 | |||
*/ | |||
#if (defined(APPCONFIG_OS_MAC) || defined(APPCONFIG_OS_WIN)) && !defined(BUILD_BRIDGE) | |||
# 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)) && !defined(BUILD_BRIDGE) | |||
# 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 | |||
// #define JUCE_DISABLE_ASSERTIONS 1 | |||
#else | |||
#define JUCE_CHECK_MEMORY_LEAKS 0 | |||
// #define JUCE_DISABLE_ASSERTIONS 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 | |||
/** Config: JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
If disabled, this will turn off dangerous static globals like String::empty, var::null, etc | |||
which can cause nasty order-of-initialisation problems if they are referenced during static | |||
constructor code. | |||
*/ | |||
#define JUCE_ALLOW_STATIC_NULL_VARIABLES 0 | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// juce_data_structures | |||
// nothing here | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// juce_dsp | |||
//============================================================================== | |||
/** Config: JUCE_ASSERTION_FIRFILTER | |||
When this flag is enabled, an assertion will be generated during the | |||
execution of DEBUG configurations if you use a FIRFilter class to process | |||
FIRCoefficients with a size higher than 128, to tell you that's it would be | |||
more efficient to use the Convolution class instead. It is enabled by | |||
default, but you may want to disable it if you really want to process such | |||
a filter in the time domain. | |||
*/ | |||
#define JUCE_ASSERTION_FIRFILTER 0 | |||
/** Config: JUCE_DSP_USE_INTEL_MKL | |||
If this flag is set, then JUCE will use Intel's MKL for JUCE's FFT and | |||
convolution classes. | |||
If you're using the Projucer's Visual Studio exporter, you should also set | |||
the "Use MKL Library (oneAPI)" option in the exporter settings to | |||
"Sequential" or "Parallel". If you're not using the Visual Studio exporter, | |||
the folder containing the mkl_dfti.h header must be in your header search | |||
paths, and you must link against all the necessary MKL libraries. | |||
*/ | |||
#define JUCE_DSP_USE_INTEL_MKL 0 | |||
/** Config: JUCE_DSP_USE_SHARED_FFTW | |||
If this flag is set, then JUCE will search for the fftw shared libraries | |||
at runtime and use the library for JUCE's FFT and convolution classes. | |||
If the library is not found, then JUCE's fallback FFT routines will be used. | |||
This is especially useful on linux as fftw often comes pre-installed on | |||
popular linux distros. | |||
You must respect the FFTW license when enabling this option. | |||
*/ | |||
#define JUCE_DSP_USE_SHARED_FFTW 0 | |||
/** Config: JUCE_DSP_USE_STATIC_FFTW | |||
If this flag is set, then JUCE will use the statically linked fftw libraries | |||
for JUCE's FFT and convolution classes. | |||
You must add the fftw header/library folder to the extra header/library search | |||
paths of your JUCE project. You also need to add the fftw library itself | |||
to the extra libraries supplied to your JUCE project during linking. | |||
You must respect the FFTW license when enabling this option. | |||
*/ | |||
#define JUCE_DSP_USE_STATIC_FFTW 0 | |||
/** Config: JUCE_DSP_ENABLE_SNAP_TO_ZERO | |||
Enables code in the dsp module to avoid floating point denormals during the | |||
processing of some of the dsp module's filters. | |||
Enabling this will add a slight performance overhead to the DSP module's | |||
filters and algorithms. If your audio app already disables denormals altogether | |||
(for example, by using the ScopedNoDenormals class or the | |||
FloatVectorOperations::disableDenormalisedNumberSupport method), then you | |||
can safely disable this flag to shave off a few cpu cycles from the DSP module's | |||
filters and algorithms. | |||
*/ | |||
#define JUCE_DSP_ENABLE_SNAP_TO_ZERO 0 | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// juce_events | |||
// 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 0 | |||
#define JUCE_INCLUDE_PNGLIB_CODE 1 | |||
#define JUCE_INCLUDE_JPEGLIB_CODE 1 | |||
#define USE_COREGRAPHICS_RENDERING 0 | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// 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 |
@@ -20,17 +20,4 @@ clean: | |||
$(MAKE) clean -C ysfx | |||
$(MAKE) clean -C zita-resampler | |||
ifneq ($(STATIC_PLUGIN_TARGET),true) | |||
$(MAKE) clean -C carla_juce | |||
$(MAKE) clean -C juce_audio_basics | |||
$(MAKE) clean -C juce_audio_devices | |||
$(MAKE) clean -C juce_audio_processors | |||
$(MAKE) clean -C juce_core | |||
$(MAKE) clean -C juce_data_structures | |||
$(MAKE) clean -C juce_events | |||
$(MAKE) clean -C juce_graphics | |||
$(MAKE) clean -C juce_gui_basics | |||
$(MAKE) clean -C juce_gui_extra | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -1,61 +0,0 @@ | |||
#!/usr/bin/make -f | |||
# Makefile for carla_juce # | |||
# ---------------------- # | |||
# Created by falkTX | |||
# | |||
CWD=../.. | |||
MODULENAME=carla_juce | |||
include ../Makefile.mk | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
BUILD_CXX_FLAGS += -I.. -std=gnu++14 | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
ifeq ($(MACOS),true) | |||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||
else | |||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
all: $(MODULEDIR)/$(MODULENAME).a | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
clean: | |||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||
debug: | |||
$(MAKE) DEBUG=true | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
-@mkdir -p $(MODULEDIR) | |||
@echo "Creating $(MODULENAME).a" | |||
$(SILENT)rm -f $@ | |||
$(SILENT)$(AR) crs $@ $^ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
-include $(OBJS:%.o=%.d) | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -1,184 +0,0 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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 USING_JUCE | |||
# error this file is not meant to be built if not using juce! | |||
#endif | |||
#include "carla_juce.h" | |||
#include "CarlaUtils.hpp" | |||
#include "juce_events/juce_events.h" | |||
#if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
# include "juce_gui_basics/juce_gui_basics.h" | |||
#endif | |||
namespace juce { | |||
bool dispatchNextMessageOnSystemQueue(bool returnIfNoPendingMessages); | |||
} | |||
namespace CarlaJUCE { | |||
void initialiseJuce_GUI() | |||
{ | |||
juce::initialiseJuce_GUI(); | |||
#if !(defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)) | |||
juce::MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
#endif | |||
} | |||
void idleJuce_GUI() | |||
{ | |||
#if !(defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)) | |||
juce::dispatchNextMessageOnSystemQueue(true); | |||
#endif | |||
} | |||
void shutdownJuce_GUI() | |||
{ | |||
juce::shutdownJuce_GUI(); | |||
} | |||
const char* getVersion() | |||
{ | |||
static const juce::String version(juce::SystemStats::getJUCEVersion()); | |||
return version.toRawUTF8(); | |||
} | |||
static int numScopedInitInstances = 0; | |||
ScopedJuceInitialiser_GUI::ScopedJuceInitialiser_GUI() | |||
{ | |||
if (numScopedInitInstances++ == 0) | |||
initialiseJuce_GUI(); | |||
} | |||
ScopedJuceInitialiser_GUI::~ScopedJuceInitialiser_GUI() | |||
{ | |||
if (--numScopedInitInstances == 0) | |||
shutdownJuce_GUI(); | |||
} | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
ReferenceCountedJuceMessageMessager::ReferenceCountedJuceMessageMessager() | |||
{ | |||
CARLA_SAFE_ASSERT(numScopedInitInstances == 0); | |||
} | |||
ReferenceCountedJuceMessageMessager::~ReferenceCountedJuceMessageMessager() | |||
{ | |||
CARLA_SAFE_ASSERT(numScopedInitInstances == 0); | |||
} | |||
void ReferenceCountedJuceMessageMessager::incRef() const | |||
{ | |||
if (numScopedInitInstances++ == 0) | |||
{ | |||
juce::initialiseJuce_GUI(); | |||
juce::MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
} | |||
} | |||
void ReferenceCountedJuceMessageMessager::decRef() const | |||
{ | |||
if (--numScopedInitInstances == 0) | |||
{ | |||
juce::shutdownJuce_GUI(); | |||
} | |||
} | |||
void setMessageManagerForThisThread() | |||
{ | |||
juce::MessageManager* const msgMgr = juce::MessageManager::getInstanceWithoutCreating(); | |||
CARLA_SAFE_ASSERT_RETURN(msgMgr != nullptr,); | |||
if (! msgMgr->isThisTheMessageThread()) | |||
{ | |||
try { | |||
msgMgr->setCurrentThreadAsMessageThread(); | |||
} CARLA_SAFE_EXCEPTION_RETURN("setCurrentThreadAsMessageThread",); | |||
} | |||
} | |||
void dispatchMessageManagerMessages() | |||
{ | |||
const juce::MessageManagerLock mml; | |||
juce::dispatchNextMessageOnSystemQueue(true); | |||
} | |||
#endif | |||
#ifdef USE_STANDALONE_JUCE_APPLICATION | |||
static std::function<void()> sIdleFn; | |||
static volatile bool* sClosedSignalPtr; | |||
class CarlaJuceApp : public juce::JUCEApplication, | |||
private juce::Timer | |||
{ | |||
public: | |||
CarlaJuceApp() {} | |||
~CarlaJuceApp() {} | |||
void initialise(const juce::String&) override | |||
{ | |||
startTimer(8); | |||
} | |||
void shutdown() override | |||
{ | |||
*sClosedSignalPtr = true; | |||
stopTimer(); | |||
} | |||
const juce::String getApplicationName() override | |||
{ | |||
return "CarlaPlugin"; | |||
} | |||
const juce::String getApplicationVersion() override | |||
{ | |||
return CARLA_VERSION_STRING; | |||
} | |||
void timerCallback() override | |||
{ | |||
sIdleFn(); | |||
if (*sClosedSignalPtr) | |||
{ | |||
quit(); | |||
*sClosedSignalPtr = false; | |||
} | |||
} | |||
}; | |||
static juce::JUCEApplicationBase* juce_CreateApplication() { return new CarlaJuceApp(); } | |||
void setupAndUseMainApplication(std::function<void()> idleFn, volatile bool* closedSignalPtr) | |||
{ | |||
#ifndef CARLA_OS_WIN | |||
static const int argc = 0; | |||
static const char* argv[] = {}; | |||
#endif | |||
sIdleFn = idleFn; | |||
sClosedSignalPtr = closedSignalPtr; | |||
juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; | |||
juce::JUCEApplicationBase::main(JUCE_MAIN_FUNCTION_ARGS); | |||
} | |||
#endif | |||
} // namespace CarlaJUCE |
@@ -1,70 +0,0 @@ | |||
/* | |||
* Carla Plugin Host | |||
* Copyright (C) 2011-2022 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_HPP_INCLUDED | |||
#define CARLA_JUCE_HPP_INCLUDED | |||
#include "AppConfig.h" | |||
#include "CarlaDefines.h" | |||
#ifdef USING_JUCE | |||
# if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
# define USE_STANDALONE_JUCE_APPLICATION | |||
# else | |||
# define USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
# endif | |||
#endif | |||
#ifdef USE_STANDALONE_JUCE_APPLICATION | |||
# include <functional> | |||
#endif | |||
namespace juce { | |||
class MessageManager; | |||
} | |||
namespace CarlaJUCE { | |||
void initialiseJuce_GUI(); | |||
void idleJuce_GUI(); | |||
void shutdownJuce_GUI(); | |||
const char* getVersion(); | |||
struct ScopedJuceInitialiser_GUI { | |||
ScopedJuceInitialiser_GUI(); | |||
~ScopedJuceInitialiser_GUI(); | |||
}; | |||
#ifdef USE_REFCOUNTER_JUCE_MESSAGE_MANAGER | |||
struct ReferenceCountedJuceMessageMessager { | |||
ReferenceCountedJuceMessageMessager(); | |||
~ReferenceCountedJuceMessageMessager(); | |||
void incRef() const; | |||
void decRef() const; | |||
}; | |||
void setMessageManagerForThisThread(); | |||
void dispatchMessageManagerMessages(); | |||
#endif | |||
#ifdef USE_STANDALONE_JUCE_APPLICATION | |||
void setupAndUseMainApplication(std::function<void()> idleFn, volatile bool* closedSignalPtr); | |||
#endif | |||
} // namespace CarlaJUCE | |||
#endif // CARLA_JUCE_HPP_INCLUDED |
@@ -1,70 +0,0 @@ | |||
#!/usr/bin/make -f | |||
# Makefile for juce_audio_basics # | |||
# ------------------------------ # | |||
# Created by falkTX | |||
# | |||
CWD=../.. | |||
MODULENAME=juce_audio_basics | |||
include ../Makefile.mk | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
BUILD_CXX_FLAGS += $(JUCE_AUDIO_BASICS_FLAGS) -I.. -std=gnu++14 | |||
# needed for older mingw, crashes on optimized float operations | |||
ifeq ($(WINDOWS),true) | |||
ifneq (,$(findstring i686,$(TARGET_MACHINE))) | |||
BUILD_CXX_FLAGS += -mpreferred-stack-boundary=2 | |||
else | |||
32BIT_FLAGS += -mpreferred-stack-boundary=2 | |||
endif | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
ifeq ($(MACOS),true) | |||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||
else | |||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||
endif | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
all: $(MODULEDIR)/$(MODULENAME).a | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
clean: | |||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||
debug: | |||
$(MAKE) DEBUG=true | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||
-@mkdir -p $(MODULEDIR) | |||
@echo "Creating $(MODULENAME).a" | |||
$(SILENT)rm -f $@ | |||
$(SILENT)$(AR) crs $@ $^ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling $<" | |||
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
-include $(OBJS:%.o=%.d) | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -1,593 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
A subclass of AudioPlayHead can supply information about the position and | |||
status of a moving play head during audio playback. | |||
One of these can be supplied to an AudioProcessor object so that it can find | |||
out about the position of the audio that it is rendering. | |||
@see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API AudioPlayHead | |||
{ | |||
protected: | |||
//============================================================================== | |||
AudioPlayHead() = default; | |||
public: | |||
virtual ~AudioPlayHead() = default; | |||
//============================================================================== | |||
/** Frame rate types. */ | |||
enum FrameRateType | |||
{ | |||
fps23976 = 0, | |||
fps24 = 1, | |||
fps25 = 2, | |||
fps2997 = 3, | |||
fps30 = 4, | |||
fps2997drop = 5, | |||
fps30drop = 6, | |||
fps60 = 7, | |||
fps60drop = 8, | |||
fpsUnknown = 99 | |||
}; | |||
/** More descriptive frame rate type. */ | |||
class JUCE_API FrameRate | |||
{ | |||
public: | |||
/** Creates a frame rate with a base rate of 0. */ | |||
FrameRate() = default; | |||
/** Creates a FrameRate instance from a FrameRateType. */ | |||
FrameRate (FrameRateType type) : FrameRate (fromType (type)) {} | |||
/** Gets the FrameRateType that matches the state of this FrameRate. | |||
Returns fpsUnknown if this FrameRate cannot be represented by any of the | |||
other enum fields. | |||
*/ | |||
FrameRateType getType() const | |||
{ | |||
switch (base) | |||
{ | |||
case 24: return pulldown ? fps23976 : fps24; | |||
case 25: return fps25; | |||
case 30: return pulldown ? (drop ? fps2997drop : fps2997) | |||
: (drop ? fps30drop : fps30); | |||
case 60: return drop ? fps60drop : fps60; | |||
} | |||
return fpsUnknown; | |||
} | |||
/** Returns the plain rate, without taking pulldown into account. */ | |||
int getBaseRate() const { return base; } | |||
/** Returns true if drop-frame timecode is in use. */ | |||
bool isDrop() const { return drop; } | |||
/** Returns true if the effective framerate is actually equal to the base rate divided by 1.001 */ | |||
bool isPullDown() const { return pulldown; } | |||
/** Returns the actual rate described by this object, taking pulldown into account. */ | |||
double getEffectiveRate() const { return pulldown ? (double) base / 1.001 : (double) base; } | |||
/** Returns a copy of this object with the specified base rate. */ | |||
JUCE_NODISCARD FrameRate withBaseRate (int x) const { return with (&FrameRate::base, x); } | |||
/** Returns a copy of this object with drop frames enabled or disabled, as specified. */ | |||
JUCE_NODISCARD FrameRate withDrop (bool x = true) const { return with (&FrameRate::drop, x); } | |||
/** Returns a copy of this object with pulldown enabled or disabled, as specified. */ | |||
JUCE_NODISCARD FrameRate withPullDown (bool x = true) const { return with (&FrameRate::pulldown, x); } | |||
/** Returns true if this instance is equal to other. */ | |||
bool operator== (const FrameRate& other) const | |||
{ | |||
const auto tie = [] (const FrameRate& x) { return std::tie (x.base, x.drop, x.pulldown); }; | |||
return tie (*this) == tie (other); | |||
} | |||
/** Returns true if this instance is not equal to other. */ | |||
bool operator!= (const FrameRate& other) const { return ! (*this == other); } | |||
private: | |||
static FrameRate fromType (FrameRateType type) | |||
{ | |||
switch (type) | |||
{ | |||
case fps23976: return FrameRate().withBaseRate (24).withPullDown(); | |||
case fps24: return FrameRate().withBaseRate (24); | |||
case fps25: return FrameRate().withBaseRate (25); | |||
case fps2997: return FrameRate().withBaseRate (30).withPullDown(); | |||
case fps30: return FrameRate().withBaseRate (30); | |||
case fps2997drop: return FrameRate().withBaseRate (30).withDrop().withPullDown(); | |||
case fps30drop: return FrameRate().withBaseRate (30).withDrop(); | |||
case fps60: return FrameRate().withBaseRate (60); | |||
case fps60drop: return FrameRate().withBaseRate (60).withDrop(); | |||
case fpsUnknown: break; | |||
} | |||
return {}; | |||
} | |||
template <typename Member, typename Value> | |||
FrameRate with (Member&& member, Value&& value) const | |||
{ | |||
auto copy = *this; | |||
copy.*member = std::forward<Value> (value); | |||
return copy; | |||
} | |||
int base = 0; | |||
bool drop = false, pulldown = false; | |||
}; | |||
/** Describes a musical time signature. | |||
@see PositionInfo::getTimeSignature() PositionInfo::setTimeSignature() | |||
*/ | |||
struct JUCE_API TimeSignature | |||
{ | |||
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
int numerator = 4; | |||
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
int denominator = 4; | |||
bool operator== (const TimeSignature& other) const | |||
{ | |||
const auto tie = [] (auto& x) { return std::tie (x.numerator, x.denominator); }; | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const TimeSignature& other) const | |||
{ | |||
return ! operator== (other); | |||
} | |||
}; | |||
/** Holds the begin and end points of a looped region. | |||
@see PositionInfo::getIsLooping() PositionInfo::setIsLooping() PositionInfo::getLoopPoints() PositionInfo::setLoopPoints() | |||
*/ | |||
struct JUCE_API LoopPoints | |||
{ | |||
/** The current cycle start position in units of quarter-notes. */ | |||
double ppqStart = 0; | |||
/** The current cycle end position in units of quarter-notes. */ | |||
double ppqEnd = 0; | |||
bool operator== (const LoopPoints& other) const | |||
{ | |||
const auto tie = [] (auto& x) { return std::tie (x.ppqStart, x.ppqEnd); }; | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const LoopPoints& other) const | |||
{ | |||
return ! operator== (other); | |||
} | |||
}; | |||
//============================================================================== | |||
/** This type is deprecated; prefer PositionInfo instead. | |||
Some position info may be unavailable, depending on the host or plugin format. | |||
Unfortunately, CurrentPositionInfo doesn't have any way of differentiating between | |||
default values and values that have been set explicitly. | |||
*/ | |||
struct JUCE_API CurrentPositionInfo | |||
{ | |||
/** The tempo in BPM */ | |||
double bpm = 120.0; | |||
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
int timeSigNumerator = 4; | |||
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
int timeSigDenominator = 4; | |||
/** The current play position, in samples from the start of the timeline. */ | |||
int64 timeInSamples = 0; | |||
/** The current play position, in seconds from the start of the timeline. */ | |||
double timeInSeconds = 0; | |||
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ | |||
double editOriginTime = 0; | |||
/** The current play position, in units of quarter-notes. */ | |||
double ppqPosition = 0; | |||
/** The position of the start of the last bar, in units of quarter-notes. | |||
This is the time from the start of the timeline to the start of the current | |||
bar, in ppq units. | |||
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If | |||
it's not available, the value will be 0. | |||
*/ | |||
double ppqPositionOfLastBarStart = 0; | |||
/** The video frame rate, if applicable. */ | |||
FrameRate frameRate = FrameRateType::fps23976; | |||
/** True if the transport is currently playing. */ | |||
bool isPlaying = false; | |||
/** True if the transport is currently recording. | |||
(When isRecording is true, then isPlaying will also be true). | |||
*/ | |||
bool isRecording = false; | |||
/** The current cycle start position in units of quarter-notes. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopStart = 0; | |||
/** The current cycle end position in units of quarter-notes. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopEnd = 0; | |||
/** True if the transport is currently looping. */ | |||
bool isLooping = false; | |||
//============================================================================== | |||
bool operator== (const CurrentPositionInfo& other) const noexcept | |||
{ | |||
const auto tie = [] (const CurrentPositionInfo& i) | |||
{ | |||
return std::tie (i.timeInSamples, | |||
i.ppqPosition, | |||
i.editOriginTime, | |||
i.ppqPositionOfLastBarStart, | |||
i.frameRate, | |||
i.isPlaying, | |||
i.isRecording, | |||
i.bpm, | |||
i.timeSigNumerator, | |||
i.timeSigDenominator, | |||
i.ppqLoopStart, | |||
i.ppqLoopEnd, | |||
i.isLooping); | |||
}; | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const CurrentPositionInfo& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
void resetToDefault() | |||
{ | |||
*this = CurrentPositionInfo{}; | |||
} | |||
}; | |||
//============================================================================== | |||
/** | |||
Describes the time at the start of the current audio callback. | |||
Not all hosts and plugin formats can provide all of the possible time | |||
information, so most of the getter functions in this class return | |||
an Optional that will only be engaged if the host provides the corresponding | |||
information. As a plugin developer, you should code defensively so that | |||
the plugin behaves sensibly even when the host fails to provide timing | |||
information. | |||
A default-constructed instance of this class will return nullopt from | |||
all functions that return an Optional. | |||
*/ | |||
class PositionInfo | |||
{ | |||
public: | |||
/** Returns the number of samples that have elapsed. */ | |||
Optional<int64_t> getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } | |||
/** @see getTimeInSamples() */ | |||
void setTimeInSamples (Optional<int64_t> timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } | |||
/** Returns the number of seconds that have elapsed. */ | |||
Optional<double> getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } | |||
/** @see getTimeInSamples() */ | |||
void setTimeInSeconds (Optional<double> timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } | |||
/** Returns the bpm, if available. */ | |||
Optional<double> getBpm() const { return getOptional (flagTempo, tempoBpm); } | |||
/** @see getBpm() */ | |||
void setBpm (Optional<double> bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } | |||
/** Returns the time signature, if available. */ | |||
Optional<TimeSignature> getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } | |||
/** @see getTimeSignature() */ | |||
void setTimeSignature (Optional<TimeSignature> timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } | |||
/** Returns host loop points, if available. */ | |||
Optional<LoopPoints> getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } | |||
/** @see getLoopPoints() */ | |||
void setLoopPoints (Optional<LoopPoints> loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } | |||
/** The number of bars since the beginning of the timeline. | |||
This value isn't available in all hosts or in all plugin formats. | |||
*/ | |||
Optional<int64_t> getBarCount() const { return getOptional (flagBarCount, barCount); } | |||
/** @see getBarCount() */ | |||
void setBarCount (Optional<int64_t> barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } | |||
/** The position of the start of the last bar, in units of quarter-notes. | |||
This is the time from the start of the timeline to the start of the current | |||
bar, in ppq units. | |||
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. | |||
*/ | |||
Optional<double> getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } | |||
/** @see getPpqPositionOfLastBarStart() */ | |||
void setPpqPositionOfLastBarStart (Optional<double> positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } | |||
/** The video frame rate, if available. */ | |||
Optional<FrameRate> getFrameRate() const { return getOptional (flagFrameRate, frame); } | |||
/** @see getFrameRate() */ | |||
void setFrameRate (Optional<FrameRate> frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } | |||
/** The current play position, in units of quarter-notes. */ | |||
Optional<double> getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } | |||
/** @see getPpqPosition() */ | |||
void setPpqPosition (Optional<double> ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } | |||
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ | |||
Optional<double> getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } | |||
/** @see getEditOriginTime() */ | |||
void setEditOriginTime (Optional<double> editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } | |||
/** Get the host's callback time in nanoseconds, if available. */ | |||
Optional<uint64_t> getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } | |||
/** @see getHostTimeNs() */ | |||
void setHostTimeNs (Optional<uint64_t> hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } | |||
/** True if the transport is currently playing. */ | |||
bool getIsPlaying() const { return getFlag (flagIsPlaying); } | |||
/** @see getIsPlaying() */ | |||
void setIsPlaying (bool isPlayingIn) { setFlag (flagIsPlaying, isPlayingIn); } | |||
/** True if the transport is currently recording. | |||
(When isRecording is true, then isPlaying will also be true). | |||
*/ | |||
bool getIsRecording() const { return getFlag (flagIsRecording); } | |||
/** @see getIsRecording() */ | |||
void setIsRecording (bool isRecordingIn) { setFlag (flagIsRecording, isRecordingIn); } | |||
/** True if the transport is currently looping. */ | |||
bool getIsLooping() const { return getFlag (flagIsLooping); } | |||
/** @see getIsLooping() */ | |||
void setIsLooping (bool isLoopingIn) { setFlag (flagIsLooping, isLoopingIn); } | |||
bool operator== (const PositionInfo& other) const noexcept | |||
{ | |||
const auto tie = [] (const PositionInfo& i) | |||
{ | |||
return std::make_tuple (i.getTimeInSamples(), | |||
i.getTimeInSeconds(), | |||
i.getPpqPosition(), | |||
i.getEditOriginTime(), | |||
i.getPpqPositionOfLastBarStart(), | |||
i.getFrameRate(), | |||
i.getBarCount(), | |||
i.getTimeSignature(), | |||
i.getBpm(), | |||
i.getLoopPoints(), | |||
i.getHostTimeNs(), | |||
i.getIsPlaying(), | |||
i.getIsRecording(), | |||
i.getIsLooping()); | |||
}; | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const PositionInfo& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
private: | |||
bool getFlag (int64_t flagToCheck) const | |||
{ | |||
return (flagToCheck & flags) != 0; | |||
} | |||
void setFlag (int64_t flagToCheck, bool value) | |||
{ | |||
flags = (value ? flags | flagToCheck : flags & ~flagToCheck); | |||
} | |||
template <typename Value> | |||
Optional<Value> getOptional (int64_t flagToCheck, Value value) const | |||
{ | |||
return getFlag (flagToCheck) ? makeOptional (std::move (value)) : nullopt; | |||
} | |||
template <typename Value> | |||
void setOptional (int64_t flagToCheck, Value& value, Optional<Value> opt) | |||
{ | |||
if (opt.hasValue()) | |||
value = *opt; | |||
setFlag (flagToCheck, opt.hasValue()); | |||
} | |||
enum | |||
{ | |||
flagTimeSignature = 1 << 0, | |||
flagLoopPoints = 1 << 1, | |||
flagFrameRate = 1 << 2, | |||
flagTimeSeconds = 1 << 3, | |||
flagLastBarStartPpq = 1 << 4, | |||
flagPpqPosition = 1 << 5, | |||
flagOriginTime = 1 << 6, | |||
flagTempo = 1 << 7, | |||
flagTimeSamples = 1 << 8, | |||
flagBarCount = 1 << 9, | |||
flagHostTimeNs = 1 << 10, | |||
flagIsPlaying = 1 << 11, | |||
flagIsRecording = 1 << 12, | |||
flagIsLooping = 1 << 13 | |||
}; | |||
TimeSignature timeSignature; | |||
LoopPoints loopPoints; | |||
FrameRate frame = FrameRateType::fps23976; | |||
double timeInSeconds = 0.0; | |||
double lastBarStartPpq = 0.0; | |||
double positionPpq = 0.0; | |||
double originTime = 0.0; | |||
double tempoBpm = 0.0; | |||
int64_t timeInSamples = 0; | |||
int64_t barCount = 0; | |||
uint64_t hostTimeNs = 0; | |||
int64_t flags = 0; | |||
}; | |||
//============================================================================== | |||
/** Deprecated, use getPosition() instead. | |||
Fills-in the given structure with details about the transport's | |||
position at the start of the current processing block. If this method returns | |||
false then the current play head position is not available and the given | |||
structure will be undefined. | |||
You can ONLY call this from your processBlock() method! Calling it at other | |||
times will produce undefined behaviour, as the host may not have any context | |||
in which a time would make sense, and some hosts will almost certainly have | |||
multithreading issues if it's not called on the audio thread. | |||
*/ | |||
[[deprecated ("Use getPosition instead. Not all hosts are able to provide all time position information; getPosition differentiates clearly between set and unset fields.")]] | |||
bool getCurrentPosition (CurrentPositionInfo& result) | |||
{ | |||
if (const auto pos = getPosition()) | |||
{ | |||
result.resetToDefault(); | |||
if (const auto sig = pos->getTimeSignature()) | |||
{ | |||
result.timeSigNumerator = sig->numerator; | |||
result.timeSigDenominator = sig->denominator; | |||
} | |||
if (const auto loop = pos->getLoopPoints()) | |||
{ | |||
result.ppqLoopStart = loop->ppqStart; | |||
result.ppqLoopEnd = loop->ppqEnd; | |||
} | |||
if (const auto frame = pos->getFrameRate()) | |||
result.frameRate = *frame; | |||
if (const auto timeInSeconds = pos->getTimeInSeconds()) | |||
result.timeInSeconds = *timeInSeconds; | |||
if (const auto lastBarStartPpq = pos->getPpqPositionOfLastBarStart()) | |||
result.ppqPositionOfLastBarStart = *lastBarStartPpq; | |||
if (const auto ppqPosition = pos->getPpqPosition()) | |||
result.ppqPosition = *ppqPosition; | |||
if (const auto originTime = pos->getEditOriginTime()) | |||
result.editOriginTime = *originTime; | |||
if (const auto bpm = pos->getBpm()) | |||
result.bpm = *bpm; | |||
if (const auto timeInSamples = pos->getTimeInSamples()) | |||
result.timeInSamples = *timeInSamples; | |||
result.isPlaying = pos->getIsPlaying(); | |||
result.isRecording = pos->getIsRecording(); | |||
result.isLooping = pos->getIsLooping(); | |||
return true; | |||
} | |||
return false; | |||
} | |||
/** Fetches details about the transport's position at the start of the current | |||
processing block. If this method returns nullopt then the current play head | |||
position is not available. | |||
A non-null return value just indicates that the host was able to provide | |||
*some* relevant timing information. Individual PositionInfo getters may | |||
still return nullopt. | |||
You can ONLY call this from your processBlock() method! Calling it at other | |||
times will produce undefined behaviour, as the host may not have any context | |||
in which a time would make sense, and some hosts will almost certainly have | |||
multithreading issues if it's not called on the audio thread. | |||
*/ | |||
virtual Optional<PositionInfo> getPosition() const = 0; | |||
/** Returns true if this object can control the transport. */ | |||
virtual bool canControlTransport() { return false; } | |||
/** Starts or stops the audio. */ | |||
virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); } | |||
/** Starts or stops recording the audio. */ | |||
virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); } | |||
/** Rewinds the audio. */ | |||
virtual void transportRewind() {} | |||
}; | |||
} // namespace juce |
@@ -1,741 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast<int64> (c)) | |||
{ | |||
} | |||
AudioChannelSet::AudioChannelSet (const std::initializer_list<ChannelType>& c) | |||
{ | |||
for (auto channel : c) | |||
addChannel (channel); | |||
} | |||
bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } | |||
bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||
bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||
String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||
{ | |||
if (type >= discreteChannel0) | |||
return "Discrete " + String (type - discreteChannel0 + 1); | |||
switch (type) | |||
{ | |||
case left: return NEEDS_TRANS("Left"); | |||
case right: return NEEDS_TRANS("Right"); | |||
case centre: return NEEDS_TRANS("Centre"); | |||
case LFE: return NEEDS_TRANS("LFE"); | |||
case leftSurround: return NEEDS_TRANS("Left Surround"); | |||
case rightSurround: return NEEDS_TRANS("Right Surround"); | |||
case leftCentre: return NEEDS_TRANS("Left Centre"); | |||
case rightCentre: return NEEDS_TRANS("Right Centre"); | |||
case centreSurround: return NEEDS_TRANS("Centre Surround"); | |||
case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); | |||
case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); | |||
case topMiddle: return NEEDS_TRANS("Top Middle"); | |||
case topFrontLeft: return NEEDS_TRANS("Top Front Left"); | |||
case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); | |||
case topFrontRight: return NEEDS_TRANS("Top Front Right"); | |||
case topRearLeft: return NEEDS_TRANS("Top Rear Left"); | |||
case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); | |||
case topRearRight: return NEEDS_TRANS("Top Rear Right"); | |||
case wideLeft: return NEEDS_TRANS("Wide Left"); | |||
case wideRight: return NEEDS_TRANS("Wide Right"); | |||
case LFE2: return NEEDS_TRANS("LFE 2"); | |||
case leftSurroundSide: return NEEDS_TRANS("Left Surround Side"); | |||
case rightSurroundSide: return NEEDS_TRANS("Right Surround Side"); | |||
case ambisonicW: return NEEDS_TRANS("Ambisonic W"); | |||
case ambisonicX: return NEEDS_TRANS("Ambisonic X"); | |||
case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); | |||
case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); | |||
case topSideLeft: return NEEDS_TRANS("Top Side Left"); | |||
case topSideRight: return NEEDS_TRANS("Top Side Right"); | |||
case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4"); | |||
case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5"); | |||
case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6"); | |||
case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7"); | |||
case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8"); | |||
case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9"); | |||
case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10"); | |||
case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11"); | |||
case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12"); | |||
case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13"); | |||
case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14"); | |||
case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15"); | |||
case ambisonicACN16: return NEEDS_TRANS("Ambisonic 16"); | |||
case ambisonicACN17: return NEEDS_TRANS("Ambisonic 17"); | |||
case ambisonicACN18: return NEEDS_TRANS("Ambisonic 18"); | |||
case ambisonicACN19: return NEEDS_TRANS("Ambisonic 19"); | |||
case ambisonicACN20: return NEEDS_TRANS("Ambisonic 20"); | |||
case ambisonicACN21: return NEEDS_TRANS("Ambisonic 21"); | |||
case ambisonicACN22: return NEEDS_TRANS("Ambisonic 22"); | |||
case ambisonicACN23: return NEEDS_TRANS("Ambisonic 23"); | |||
case ambisonicACN24: return NEEDS_TRANS("Ambisonic 24"); | |||
case ambisonicACN25: return NEEDS_TRANS("Ambisonic 25"); | |||
case ambisonicACN26: return NEEDS_TRANS("Ambisonic 26"); | |||
case ambisonicACN27: return NEEDS_TRANS("Ambisonic 27"); | |||
case ambisonicACN28: return NEEDS_TRANS("Ambisonic 28"); | |||
case ambisonicACN29: return NEEDS_TRANS("Ambisonic 29"); | |||
case ambisonicACN30: return NEEDS_TRANS("Ambisonic 30"); | |||
case ambisonicACN31: return NEEDS_TRANS("Ambisonic 31"); | |||
case ambisonicACN32: return NEEDS_TRANS("Ambisonic 32"); | |||
case ambisonicACN33: return NEEDS_TRANS("Ambisonic 33"); | |||
case ambisonicACN34: return NEEDS_TRANS("Ambisonic 34"); | |||
case ambisonicACN35: return NEEDS_TRANS("Ambisonic 35"); | |||
case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); | |||
case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); | |||
case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); | |||
case proximityLeft: return NEEDS_TRANS("Proximity Left"); | |||
case proximityRight: return NEEDS_TRANS("Proximity Right"); | |||
case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left"); | |||
case bottomSideRight: return NEEDS_TRANS("Bottom Side Right"); | |||
case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left"); | |||
case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre"); | |||
case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right"); | |||
case discreteChannel0: | |||
case unknown: | |||
default: break; | |||
} | |||
return "Unknown"; | |||
} | |||
String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||
{ | |||
if (type >= discreteChannel0) | |||
return String (type - discreteChannel0 + 1); | |||
switch (type) | |||
{ | |||
case left: return "L"; | |||
case right: return "R"; | |||
case centre: return "C"; | |||
case LFE: return "Lfe"; | |||
case leftSurround: return "Ls"; | |||
case rightSurround: return "Rs"; | |||
case leftCentre: return "Lc"; | |||
case rightCentre: return "Rc"; | |||
case centreSurround: return "Cs"; | |||
case leftSurroundRear: return "Lrs"; | |||
case rightSurroundRear: return "Rrs"; | |||
case topMiddle: return "Tm"; | |||
case topFrontLeft: return "Tfl"; | |||
case topFrontCentre: return "Tfc"; | |||
case topFrontRight: return "Tfr"; | |||
case topRearLeft: return "Trl"; | |||
case topRearCentre: return "Trc"; | |||
case topRearRight: return "Trr"; | |||
case wideLeft: return "Wl"; | |||
case wideRight: return "Wr"; | |||
case LFE2: return "Lfe2"; | |||
case leftSurroundSide: return "Lss"; | |||
case rightSurroundSide: return "Rss"; | |||
case ambisonicACN0: return "ACN0"; | |||
case ambisonicACN1: return "ACN1"; | |||
case ambisonicACN2: return "ACN2"; | |||
case ambisonicACN3: return "ACN3"; | |||
case ambisonicACN4: return "ACN4"; | |||
case ambisonicACN5: return "ACN5"; | |||
case ambisonicACN6: return "ACN6"; | |||
case ambisonicACN7: return "ACN7"; | |||
case ambisonicACN8: return "ACN8"; | |||
case ambisonicACN9: return "ACN9"; | |||
case ambisonicACN10: return "ACN10"; | |||
case ambisonicACN11: return "ACN11"; | |||
case ambisonicACN12: return "ACN12"; | |||
case ambisonicACN13: return "ACN13"; | |||
case ambisonicACN14: return "ACN14"; | |||
case ambisonicACN15: return "ACN15"; | |||
case ambisonicACN16: return "ACN16"; | |||
case ambisonicACN17: return "ACN17"; | |||
case ambisonicACN18: return "ACN18"; | |||
case ambisonicACN19: return "ACN19"; | |||
case ambisonicACN20: return "ACN20"; | |||
case ambisonicACN21: return "ACN21"; | |||
case ambisonicACN22: return "ACN22"; | |||
case ambisonicACN23: return "ACN23"; | |||
case ambisonicACN24: return "ACN24"; | |||
case ambisonicACN25: return "ACN25"; | |||
case ambisonicACN26: return "ACN26"; | |||
case ambisonicACN27: return "ACN27"; | |||
case ambisonicACN28: return "ACN28"; | |||
case ambisonicACN29: return "ACN29"; | |||
case ambisonicACN30: return "ACN30"; | |||
case ambisonicACN31: return "ACN31"; | |||
case ambisonicACN32: return "ACN32"; | |||
case ambisonicACN33: return "ACN33"; | |||
case ambisonicACN34: return "ACN34"; | |||
case ambisonicACN35: return "ACN35"; | |||
case topSideLeft: return "Tsl"; | |||
case topSideRight: return "Tsr"; | |||
case bottomFrontLeft: return "Bfl"; | |||
case bottomFrontCentre: return "Bfc"; | |||
case bottomFrontRight: return "Bfr"; | |||
case proximityLeft: return "Pl"; | |||
case proximityRight: return "Pr"; | |||
case bottomSideLeft: return "Bsl"; | |||
case bottomSideRight: return "Bsr"; | |||
case bottomRearLeft: return "Brl"; | |||
case bottomRearCentre: return "Brc"; | |||
case bottomRearRight: return "Brr"; | |||
case discreteChannel0: | |||
case unknown: | |||
default: break; | |||
} | |||
if (type >= ambisonicACN4 && type <= ambisonicACN35) | |||
return "ACN" + String (type - ambisonicACN4 + 4); | |||
return {}; | |||
} | |||
AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr) | |||
{ | |||
if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) | |||
return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (discreteChannel0) | |||
+ abbr.getIntValue() - 1); | |||
if (abbr == "L") return left; | |||
if (abbr == "R") return right; | |||
if (abbr == "C") return centre; | |||
if (abbr == "Lfe") return LFE; | |||
if (abbr == "Ls") return leftSurround; | |||
if (abbr == "Rs") return rightSurround; | |||
if (abbr == "Lc") return leftCentre; | |||
if (abbr == "Rc") return rightCentre; | |||
if (abbr == "Cs") return centreSurround; | |||
if (abbr == "Lrs") return leftSurroundRear; | |||
if (abbr == "Rrs") return rightSurroundRear; | |||
if (abbr == "Tm") return topMiddle; | |||
if (abbr == "Tfl") return topFrontLeft; | |||
if (abbr == "Tfc") return topFrontCentre; | |||
if (abbr == "Tfr") return topFrontRight; | |||
if (abbr == "Trl") return topRearLeft; | |||
if (abbr == "Trc") return topRearCentre; | |||
if (abbr == "Trr") return topRearRight; | |||
if (abbr == "Wl") return wideLeft; | |||
if (abbr == "Wr") return wideRight; | |||
if (abbr == "Lfe2") return LFE2; | |||
if (abbr == "Lss") return leftSurroundSide; | |||
if (abbr == "Rss") return rightSurroundSide; | |||
if (abbr == "W") return ambisonicW; | |||
if (abbr == "X") return ambisonicX; | |||
if (abbr == "Y") return ambisonicY; | |||
if (abbr == "Z") return ambisonicZ; | |||
if (abbr == "ACN0") return ambisonicACN0; | |||
if (abbr == "ACN1") return ambisonicACN1; | |||
if (abbr == "ACN2") return ambisonicACN2; | |||
if (abbr == "ACN3") return ambisonicACN3; | |||
if (abbr == "ACN4") return ambisonicACN4; | |||
if (abbr == "ACN5") return ambisonicACN5; | |||
if (abbr == "ACN6") return ambisonicACN6; | |||
if (abbr == "ACN7") return ambisonicACN7; | |||
if (abbr == "ACN8") return ambisonicACN8; | |||
if (abbr == "ACN9") return ambisonicACN9; | |||
if (abbr == "ACN10") return ambisonicACN10; | |||
if (abbr == "ACN11") return ambisonicACN11; | |||
if (abbr == "ACN12") return ambisonicACN12; | |||
if (abbr == "ACN13") return ambisonicACN13; | |||
if (abbr == "ACN14") return ambisonicACN14; | |||
if (abbr == "ACN15") return ambisonicACN15; | |||
if (abbr == "ACN16") return ambisonicACN16; | |||
if (abbr == "ACN17") return ambisonicACN17; | |||
if (abbr == "ACN18") return ambisonicACN18; | |||
if (abbr == "ACN19") return ambisonicACN19; | |||
if (abbr == "ACN20") return ambisonicACN20; | |||
if (abbr == "ACN21") return ambisonicACN21; | |||
if (abbr == "ACN22") return ambisonicACN22; | |||
if (abbr == "ACN23") return ambisonicACN23; | |||
if (abbr == "ACN24") return ambisonicACN24; | |||
if (abbr == "ACN25") return ambisonicACN25; | |||
if (abbr == "ACN26") return ambisonicACN26; | |||
if (abbr == "ACN27") return ambisonicACN27; | |||
if (abbr == "ACN28") return ambisonicACN28; | |||
if (abbr == "ACN29") return ambisonicACN29; | |||
if (abbr == "ACN30") return ambisonicACN30; | |||
if (abbr == "ACN31") return ambisonicACN31; | |||
if (abbr == "ACN32") return ambisonicACN32; | |||
if (abbr == "ACN33") return ambisonicACN33; | |||
if (abbr == "ACN34") return ambisonicACN34; | |||
if (abbr == "ACN35") return ambisonicACN35; | |||
if (abbr == "Tsl") return topSideLeft; | |||
if (abbr == "Tsr") return topSideRight; | |||
if (abbr == "Bfl") return bottomFrontLeft; | |||
if (abbr == "Bfc") return bottomFrontCentre; | |||
if (abbr == "Bfr") return bottomFrontRight; | |||
if (abbr == "Bsl") return bottomSideLeft; | |||
if (abbr == "Bsr") return bottomSideRight; | |||
if (abbr == "Brl") return bottomRearLeft; | |||
if (abbr == "Brc") return bottomRearCentre; | |||
if (abbr == "Brr") return bottomRearRight; | |||
return unknown; | |||
} | |||
String AudioChannelSet::getSpeakerArrangementAsString() const | |||
{ | |||
StringArray speakerTypes; | |||
for (auto& speaker : getChannelTypes()) | |||
{ | |||
auto name = getAbbreviatedChannelTypeName (speaker); | |||
if (name.isNotEmpty()) | |||
speakerTypes.add (name); | |||
} | |||
return speakerTypes.joinIntoString (" "); | |||
} | |||
AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str) | |||
{ | |||
AudioChannelSet set; | |||
for (auto& abbr : StringArray::fromTokens (str, true)) | |||
{ | |||
auto type = getChannelTypeFromAbbreviation (abbr); | |||
if (type != unknown) | |||
set.addChannel (type); | |||
} | |||
return set; | |||
} | |||
String AudioChannelSet::getDescription() const | |||
{ | |||
if (isDiscreteLayout()) return "Discrete #" + String (size()); | |||
if (*this == disabled()) return "Disabled"; | |||
if (*this == mono()) return "Mono"; | |||
if (*this == stereo()) return "Stereo"; | |||
if (*this == createLCR()) return "LCR"; | |||
if (*this == createLRS()) return "LRS"; | |||
if (*this == createLCRS()) return "LCRS"; | |||
if (*this == create5point0()) return "5.0 Surround"; | |||
if (*this == create5point1()) return "5.1 Surround"; | |||
if (*this == create5point1point2()) return "5.1.2 Surround"; | |||
if (*this == create5point1point4()) return "5.1.4 Surround"; | |||
if (*this == create6point0()) return "6.0 Surround"; | |||
if (*this == create6point1()) return "6.1 Surround"; | |||
if (*this == create6point0Music()) return "6.0 (Music) Surround"; | |||
if (*this == create6point1Music()) return "6.1 (Music) Surround"; | |||
if (*this == create7point0()) return "7.0 Surround"; | |||
if (*this == create7point1()) return "7.1 Surround"; | |||
if (*this == create7point0SDDS()) return "7.0 Surround SDDS"; | |||
if (*this == create7point1SDDS()) return "7.1 Surround SDDS"; | |||
if (*this == create7point0point2()) return "7.0.2 Surround"; | |||
if (*this == create7point0point4()) return "7.0.4 Surround"; | |||
if (*this == create7point1point2()) return "7.1.2 Surround"; | |||
if (*this == create7point1point4()) return "7.1.4 Surround"; | |||
if (*this == create7point1point6()) return "7.1.6 Surround"; | |||
if (*this == create9point1point6()) return "9.1.6 Surround"; | |||
if (*this == quadraphonic()) return "Quadraphonic"; | |||
if (*this == pentagonal()) return "Pentagonal"; | |||
if (*this == hexagonal()) return "Hexagonal"; | |||
if (*this == octagonal()) return "Octagonal"; | |||
// ambisonics | |||
{ | |||
auto order = getAmbisonicOrder(); | |||
if (order >= 0) | |||
{ | |||
String suffix; | |||
switch (order) | |||
{ | |||
case 1: suffix = "st"; break; | |||
case 2: suffix = "nd"; break; | |||
case 3: suffix = "rd"; break; | |||
default: suffix = "th"; break; | |||
} | |||
return String (order) + suffix + " Order Ambisonics"; | |||
} | |||
} | |||
return "Unknown"; | |||
} | |||
bool AudioChannelSet::isDiscreteLayout() const noexcept | |||
{ | |||
for (auto& speaker : getChannelTypes()) | |||
if (speaker <= ambisonicACN35) | |||
return false; | |||
return true; | |||
} | |||
int AudioChannelSet::size() const noexcept | |||
{ | |||
return channels.countNumberOfSetBits(); | |||
} | |||
AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept | |||
{ | |||
int bit = channels.findNextSetBit(0); | |||
for (int i = 0; i < index && bit >= 0; ++i) | |||
bit = channels.findNextSetBit (bit + 1); | |||
return static_cast<ChannelType> (bit); | |||
} | |||
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||
{ | |||
int idx = 0; | |||
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||
{ | |||
if (static_cast<ChannelType> (bit) == type) | |||
return idx; | |||
idx++; | |||
} | |||
return -1; | |||
} | |||
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||
{ | |||
Array<ChannelType> result; | |||
for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||
result.add (static_cast<ChannelType> (bit)); | |||
return result; | |||
} | |||
void AudioChannelSet::addChannel (ChannelType newChannel) | |||
{ | |||
const int bit = static_cast<int> (newChannel); | |||
jassert (bit >= 0 && bit < 1024); | |||
channels.setBit (bit); | |||
} | |||
void AudioChannelSet::removeChannel (ChannelType newChannel) | |||
{ | |||
const int bit = static_cast<int> (newChannel); | |||
jassert (bit >= 0 && bit < 1024); | |||
channels.clearBit (bit); | |||
} | |||
AudioChannelSet AudioChannelSet::disabled() { return {}; } | |||
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet ({ centre }); } | |||
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ({ left, right }); } | |||
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ({ left, right, centre }); } | |||
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ({ left, right, surround }); } | |||
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ({ left, right, centre, surround }); } | |||
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, centreSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, centreSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ({ left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide }); } | |||
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ({ left, right, LFE, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide }); } | |||
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre }); } | |||
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre }); } | |||
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ({ left, right, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ({ left, right, centre, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ({ left, right, centre, centreSurround, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, centreSurround, wideLeft, wideRight }); } | |||
AudioChannelSet AudioChannelSet::create5point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create5point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create7point0point4() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create9point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::ambisonic (int order) | |||
{ | |||
jassert (isPositiveAndBelow (order, 6)); | |||
if (order == 0) | |||
return AudioChannelSet ((uint32) (1 << ambisonicACN0)); | |||
AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3)); | |||
auto numAmbisonicChannels = (order + 1) * (order + 1); | |||
set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true); | |||
return set; | |||
} | |||
int AudioChannelSet::getAmbisonicOrder() const | |||
{ | |||
auto ambisonicOrder = getAmbisonicOrderForNumChannels (size()); | |||
if (ambisonicOrder >= 0) | |||
return (*this == ambisonic (ambisonicOrder) ? ambisonicOrder : -1); | |||
return -1; | |||
} | |||
AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) | |||
{ | |||
AudioChannelSet s; | |||
s.channels.setRange (discreteChannel0, numChannels, true); | |||
return s; | |||
} | |||
AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) | |||
{ | |||
if (numChannels == 1) return AudioChannelSet::mono(); | |||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||
return discreteChannels (numChannels); | |||
} | |||
AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) | |||
{ | |||
if (numChannels == 1) return AudioChannelSet::mono(); | |||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||
return {}; | |||
} | |||
Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) | |||
{ | |||
Array<AudioChannelSet> retval; | |||
if (numChannels != 0) | |||
{ | |||
retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||
retval.addArray ([numChannels]() -> Array<AudioChannelSet> | |||
{ | |||
switch (numChannels) | |||
{ | |||
case 1: | |||
return { AudioChannelSet::mono() }; | |||
case 2: | |||
return { AudioChannelSet::stereo() }; | |||
case 3: | |||
return { AudioChannelSet::createLCR(), | |||
AudioChannelSet::createLRS() }; | |||
case 4: | |||
return { AudioChannelSet::quadraphonic(), | |||
AudioChannelSet::createLCRS() }; | |||
case 5: | |||
return { AudioChannelSet::create5point0(), | |||
AudioChannelSet::pentagonal() }; | |||
case 6: | |||
return { AudioChannelSet::create5point1(), | |||
AudioChannelSet::create6point0(), | |||
AudioChannelSet::create6point0Music(), | |||
AudioChannelSet::hexagonal() }; | |||
case 7: | |||
return { AudioChannelSet::create7point0(), | |||
AudioChannelSet::create7point0SDDS(), | |||
AudioChannelSet::create6point1(), | |||
AudioChannelSet::create6point1Music() }; | |||
case 8: | |||
return { AudioChannelSet::create7point1(), | |||
AudioChannelSet::create7point1SDDS(), | |||
AudioChannelSet::octagonal(), | |||
AudioChannelSet::create5point1point2() }; | |||
case 9: | |||
return { AudioChannelSet::create7point0point2() }; | |||
case 10: | |||
return { AudioChannelSet::create5point1point4(), | |||
AudioChannelSet::create7point1point2() }; | |||
case 11: | |||
return { AudioChannelSet::create7point0point4() }; | |||
case 12: | |||
return { AudioChannelSet::create7point1point4() }; | |||
case 14: | |||
return { AudioChannelSet::create7point1point6() }; | |||
case 16: | |||
return { AudioChannelSet::create9point1point6() }; | |||
} | |||
return {}; | |||
}()); | |||
auto order = getAmbisonicOrderForNumChannels (numChannels); | |||
if (order >= 0) | |||
retval.add (AudioChannelSet::ambisonic (order)); | |||
} | |||
return retval; | |||
} | |||
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array<ChannelType>& channelArray) | |||
{ | |||
AudioChannelSet set; | |||
for (auto ch : channelArray) | |||
{ | |||
jassert (! set.channels[static_cast<int> (ch)]); | |||
set.addChannel (ch); | |||
} | |||
return set; | |||
} | |||
//============================================================================== | |||
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask) | |||
{ | |||
return AudioChannelSet (static_cast<uint32> ((dwChannelMask & ((1 << 18) - 1)) << 1)); | |||
} | |||
int32 AudioChannelSet::getWaveChannelMask() const noexcept | |||
{ | |||
if (channels.getHighestBit() > topRearRight) | |||
return -1; | |||
return (channels.toInteger() >> 1); | |||
} | |||
//============================================================================== | |||
int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels) | |||
{ | |||
auto sqrtMinusOne = std::sqrt (static_cast<float> (numChannels)) - 1.0f; | |||
auto ambisonicOrder = jmax (0, static_cast<int> (std::floor (sqrtMinusOne))); | |||
if (ambisonicOrder > 5) | |||
return -1; | |||
return (static_cast<float> (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1); | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class AudioChannelSetUnitTest : public UnitTest | |||
{ | |||
public: | |||
AudioChannelSetUnitTest() | |||
: UnitTest ("AudioChannelSetUnitTest", UnitTestCategories::audio) | |||
{} | |||
void runTest() override | |||
{ | |||
auto max = AudioChannelSet::maxChannelsOfNamedLayout; | |||
beginTest ("maxChannelsOfNamedLayout is non-discrete"); | |||
expect (AudioChannelSet::channelSetsWithNumberOfChannels (max).size() >= 2); | |||
beginTest ("channelSetsWithNumberOfChannels returns correct speaker count"); | |||
{ | |||
for (auto ch = 1; ch <= max; ++ch) | |||
{ | |||
auto channelSets = AudioChannelSet::channelSetsWithNumberOfChannels (ch); | |||
for (auto set : channelSets) | |||
expect (set.size() == ch); | |||
} | |||
} | |||
beginTest ("Ambisonics"); | |||
{ | |||
uint64 mask = 0; | |||
mask |= (1ull << AudioChannelSet::ambisonicACN0); | |||
checkAmbisonic (mask, 0, "0th Order Ambisonics"); | |||
mask |= (1ull << AudioChannelSet::ambisonicACN1) | (1ull << AudioChannelSet::ambisonicACN2) | (1ull << AudioChannelSet::ambisonicACN3); | |||
checkAmbisonic (mask, 1, "1st Order Ambisonics"); | |||
mask |= (1ull << AudioChannelSet::ambisonicACN4) | (1ull << AudioChannelSet::ambisonicACN5) | (1ull << AudioChannelSet::ambisonicACN6) | |||
| (1ull << AudioChannelSet::ambisonicACN7) | (1ull << AudioChannelSet::ambisonicACN8); | |||
checkAmbisonic (mask, 2, "2nd Order Ambisonics"); | |||
mask |= (1ull << AudioChannelSet::ambisonicACN9) | (1ull << AudioChannelSet::ambisonicACN10) | (1ull << AudioChannelSet::ambisonicACN11) | |||
| (1ull << AudioChannelSet::ambisonicACN12) | (1ull << AudioChannelSet::ambisonicACN13) | (1ull << AudioChannelSet::ambisonicACN14) | |||
| (1ull << AudioChannelSet::ambisonicACN15); | |||
checkAmbisonic (mask, 3, "3rd Order Ambisonics"); | |||
mask |= (1ull << AudioChannelSet::ambisonicACN16) | (1ull << AudioChannelSet::ambisonicACN17) | (1ull << AudioChannelSet::ambisonicACN18) | |||
| (1ull << AudioChannelSet::ambisonicACN19) | (1ull << AudioChannelSet::ambisonicACN20) | (1ull << AudioChannelSet::ambisonicACN21) | |||
| (1ull << AudioChannelSet::ambisonicACN22) | (1ull << AudioChannelSet::ambisonicACN23) | (1ull << AudioChannelSet::ambisonicACN24); | |||
checkAmbisonic (mask, 4, "4th Order Ambisonics"); | |||
mask |= (1ull << AudioChannelSet::ambisonicACN25) | (1ull << AudioChannelSet::ambisonicACN26) | (1ull << AudioChannelSet::ambisonicACN27) | |||
| (1ull << AudioChannelSet::ambisonicACN28) | (1ull << AudioChannelSet::ambisonicACN29) | (1ull << AudioChannelSet::ambisonicACN30) | |||
| (1ull << AudioChannelSet::ambisonicACN31) | (1ull << AudioChannelSet::ambisonicACN32) | (1ull << AudioChannelSet::ambisonicACN33) | |||
| (1ull << AudioChannelSet::ambisonicACN34) | (1ull << AudioChannelSet::ambisonicACN35); | |||
checkAmbisonic (mask, 5, "5th Order Ambisonics"); | |||
} | |||
} | |||
private: | |||
void checkAmbisonic (uint64 mask, int order, const char* layoutName) | |||
{ | |||
auto expected = AudioChannelSet::ambisonic (order); | |||
auto numChannels = expected.size(); | |||
expect (numChannels == BigInteger ((int64) mask).countNumberOfSetBits()); | |||
expect (channelSetFromMask (mask) == expected); | |||
expect (order == expected.getAmbisonicOrder()); | |||
expect (expected.getDescription() == layoutName); | |||
auto layouts = AudioChannelSet::channelSetsWithNumberOfChannels (numChannels); | |||
expect (layouts.contains (expected)); | |||
for (auto layout : layouts) | |||
expect (layout.getAmbisonicOrder() == (layout == expected ? order : -1)); | |||
} | |||
static AudioChannelSet channelSetFromMask (uint64 mask) | |||
{ | |||
Array<AudioChannelSet::ChannelType> channels; | |||
for (int bit = 0; bit <= 62; ++bit) | |||
if ((mask & (1ull << bit)) != 0) | |||
channels.add (static_cast<AudioChannelSet::ChannelType> (bit)); | |||
return AudioChannelSet::channelSetWithChannels (channels); | |||
} | |||
}; | |||
static AudioChannelSetUnitTest audioChannelSetUnitTest; | |||
#endif | |||
} // namespace juce |
@@ -1,525 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Represents a set of audio channel types. | |||
For example, you might have a set of left + right channels, which is a stereo | |||
channel set. It is a collection of values from the AudioChannelSet::ChannelType | |||
enum, where each type may only occur once within the set. | |||
The documentation below lists which AudioChannelSet corresponds to which native | |||
layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio | |||
are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" | |||
in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas | |||
AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's | |||
kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag | |||
as an indication to the actual layout of the speakers. | |||
@see Bus | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API AudioChannelSet | |||
{ | |||
public: | |||
/** Creates an empty channel set. | |||
You can call addChannel to add channels to the set. | |||
*/ | |||
AudioChannelSet() = default; | |||
/** Creates a zero-channel set which can be used to indicate that a | |||
bus is disabled. */ | |||
static AudioChannelSet JUCE_CALLTYPE disabled(); | |||
//============================================================================== | |||
/** Creates a one-channel mono set (centre). | |||
Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE mono(); | |||
/** Creates a set containing a stereo set (left, right). | |||
Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE stereo(); | |||
//============================================================================== | |||
/** Creates a set containing an LCR set (left, right, centre). | |||
Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) | |||
This format is referred to as "LRC" in Cubase. | |||
This format is referred to as "LCR" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE createLCR(); | |||
/** Creates a set containing an LRS set (left, right, surround). | |||
Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) | |||
This format is referred to as "LRS" in Cubase. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE createLRS(); | |||
/** Creates a set containing an LCRS set (left, right, centre, surround). | |||
Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) | |||
This format is referred to as "LCRS (Pro Logic)" in Logic Pro. | |||
This format is referred to as "LRCS" in Cubase. | |||
This format is referred to as "LCRS" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE createLCRS(); | |||
//============================================================================== | |||
/** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). | |||
Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) | |||
This format is referred to as "5.0" in Cubase. | |||
This format is referred to as "5.0" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point0(); | |||
/** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). | |||
Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) | |||
This format is referred to as "5.1 (ITU 775)" in Logic Pro. | |||
This format is referred to as "5.1" in Cubase. | |||
This format is referred to as "5.1" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point1(); | |||
/** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). | |||
Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) | |||
Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". | |||
This format is referred to as "6.0 Cine" in Cubase. | |||
This format is referred to as "6.0" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create6point0(); | |||
/** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). | |||
Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) | |||
This format is referred to as "6.1" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create6point1(); | |||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). | |||
Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) | |||
This format is referred to as "6.0 Music" in Cubase. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create6point0Music(); | |||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). | |||
Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create6point1Music(); | |||
/** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). | |||
Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) | |||
This format is referred to as "7.0" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point0(); | |||
/** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). | |||
Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) | |||
This format is referred to as "7.0 SDDS" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS(); | |||
/** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). | |||
Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) | |||
This format is referred to as "7.1 (3/4.1)" in Logic Pro. | |||
This format is referred to as "7.1" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1(); | |||
/** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). | |||
Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) | |||
This format is referred to as "7.1 (SDDS)" in Logic Pro. | |||
This format is referred to as "7.1 SDDS" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); | |||
/** Creates a set for a 5.1.2 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight). | |||
Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_2 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point1point2(); | |||
/** Creates a set for a 5.1.4 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_4 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point1point4(); | |||
/** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight). | |||
Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point0point2(); | |||
/** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight). | |||
Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), kAudioChannelLayoutTag_Atmos_7_1_2 (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point2(); | |||
/** Creates a set for Dolby Atmos 7.0.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: n/a (VST), n/a (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point0point4(); | |||
/** Creates a set for Dolby Atmos 7.1.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: k71_4 (VST), n/a (AAX), kAudioChannelLayoutTag_Atmos_7_1_4 (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point4(); | |||
/** Creates a set for Dolby Atmos 7.1.6 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). | |||
Is equivalent to: k71_6 (VST), n/a (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point6(); | |||
/** Creates a set for a 9.1.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). | |||
Note that the VST3 layout arranges the front speakers "L Lc C Rc R", but the JUCE layout | |||
uses the arrangement "wideLeft left centre right wideRight". To maintain the relative | |||
positions of the speakers, the channels will be remapped accordingly. This means that the | |||
VST3 host's "L" channel will be received on a JUCE plugin's "wideLeft" channel, the | |||
"Lc" channel will be received on a JUCE plugin's "left" channel, and so on. | |||
Is equivalent to: k91_6 (VST3), kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create9point1point6(); | |||
//============================================================================== | |||
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||
Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) | |||
This format is referred to as "Quadraphonic" in Logic Pro. | |||
This format is referred to as "Quadro" in Cubase. | |||
This format is referred to as "Quad" in Pro Tools. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE quadraphonic(); | |||
/** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). | |||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE pentagonal(); | |||
/** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). | |||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE hexagonal(); | |||
/** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). | |||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE octagonal(); | |||
//============================================================================== | |||
/** Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order. | |||
Is equivalent to: kAmbiXXXOrderACN (VST), AAX_eStemFormat_Ambi_XXX_ACN (AAX), kAudioChannelLayoutTag_HOA_ACN_SN3D (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE ambisonic (int order = 1); | |||
/** Returns the order of the ambisonic layout represented by this AudioChannelSet. If the | |||
AudioChannelSet is not an ambisonic layout, then this method will return -1. | |||
*/ | |||
int getAmbisonicOrder() const; | |||
//============================================================================== | |||
/** Creates a set of untyped discrete channels. */ | |||
static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels); | |||
/** Create a canonical channel set for a given number of channels. | |||
For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ | |||
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels); | |||
/** Create a channel set for a given number of channels which is non-discrete. | |||
If numChannels is larger than the number of channels of the surround format | |||
with the maximum amount of channels (currently 7.1 Surround), then this | |||
function returns an empty set.*/ | |||
static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels); | |||
/** Return an array of channel sets which have a given number of channels */ | |||
static Array<AudioChannelSet> JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels); | |||
//============================================================================== | |||
/** Represents different audio channel types. */ | |||
enum ChannelType | |||
{ | |||
unknown = 0, /**< Unknown channel type. */ | |||
//============================================================================== | |||
left = 1, /**< L channel. */ | |||
right = 2, /**< R channel. */ | |||
centre = 3, /**< C channel. (Sometimes M for mono) */ | |||
//============================================================================== | |||
LFE = 4, /**< LFE channel. */ | |||
leftSurround = 5, /**< Ls channel. */ | |||
rightSurround = 6, /**< Rs channel. */ | |||
leftCentre = 7, /**< Lc (AAX/VST), Lc used as Lss in AU for most layouts. */ | |||
rightCentre = 8, /**< Rc (AAX/VST), Rc used as Rss in AU for most layouts. */ | |||
centreSurround = 9, /**< Cs/S channel. */ | |||
surround = centreSurround, /**< Same as Centre Surround channel. */ | |||
leftSurroundSide = 10, /**< Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) channel. */ | |||
rightSurroundSide = 11, /**< Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) channel. */ | |||
topMiddle = 12, /**< Top Middle channel. */ | |||
topFrontLeft = 13, /**< Top Front Left channel. */ | |||
topFrontCentre = 14, /**< Top Front Centre channel. */ | |||
topFrontRight = 15, /**< Top Front Right channel. */ | |||
topRearLeft = 16, /**< Top Rear Left channel. */ | |||
topRearCentre = 17, /**< Top Rear Centre channel. */ | |||
topRearRight = 18, /**< Top Rear Right channel. */ | |||
LFE2 = 19, /**< Second LFE channel. */ | |||
leftSurroundRear = 20, /**< Lsr (AAX), Lcs (VST), Rls (AU) channel. */ | |||
rightSurroundRear = 21, /**< Rsr (AAX), Rcs (VST), Rrs (AU) channel. */ | |||
wideLeft = 22, /**< Wide Left channel. */ | |||
wideRight = 23, /**< Wide Right channel. */ | |||
//============================================================================== | |||
// Used by Dolby Atmos 7.0.2 and 7.1.2 | |||
topSideLeft = 28, /**< Lts (AAX), Tsl (VST), Ltm (AU) channel for Dolby Atmos. */ | |||
topSideRight = 29, /**< Rts (AAX), Tsr (VST), Rtm (AU) channel for Dolby Atmos. */ | |||
//============================================================================== | |||
// Ambisonic ACN formats - all channels are SN3D normalised | |||
// zero-th and first-order ambisonic ACN | |||
ambisonicACN0 = 24, /**< Zero-th ambisonic channel number 0. */ | |||
ambisonicACN1 = 25, /**< First-order ambisonic channel number 1. */ | |||
ambisonicACN2 = 26, /**< First-order ambisonic channel number 2. */ | |||
ambisonicACN3 = 27, /**< First-order ambisonic channel number 3. */ | |||
// second-order ambisonic | |||
ambisonicACN4 = 30, /**< Second-order ambisonic channel number 4. */ | |||
ambisonicACN5 = 31, /**< Second-order ambisonic channel number 5. */ | |||
ambisonicACN6 = 32, /**< Second-order ambisonic channel number 6. */ | |||
ambisonicACN7 = 33, /**< Second-order ambisonic channel number 7. */ | |||
ambisonicACN8 = 34, /**< Second-order ambisonic channel number 8. */ | |||
// third-order ambisonic | |||
ambisonicACN9 = 35, /**< Third-order ambisonic channel number 9. */ | |||
ambisonicACN10 = 36, /**< Third-order ambisonic channel number 10. */ | |||
ambisonicACN11 = 37, /**< Third-order ambisonic channel number 11. */ | |||
ambisonicACN12 = 38, /**< Third-order ambisonic channel number 12. */ | |||
ambisonicACN13 = 39, /**< Third-order ambisonic channel number 13. */ | |||
ambisonicACN14 = 40, /**< Third-order ambisonic channel number 14. */ | |||
ambisonicACN15 = 41, /**< Third-order ambisonic channel number 15. */ | |||
// fourth-order ambisonic | |||
ambisonicACN16 = 42, /**< Fourth-order ambisonic channel number 16. */ | |||
ambisonicACN17 = 43, /**< Fourth-order ambisonic channel number 17. */ | |||
ambisonicACN18 = 44, /**< Fourth-order ambisonic channel number 18. */ | |||
ambisonicACN19 = 45, /**< Fourth-order ambisonic channel number 19. */ | |||
ambisonicACN20 = 46, /**< Fourth-order ambisonic channel number 20. */ | |||
ambisonicACN21 = 47, /**< Fourth-order ambisonic channel number 21. */ | |||
ambisonicACN22 = 48, /**< Fourth-order ambisonic channel number 22. */ | |||
ambisonicACN23 = 49, /**< Fourth-order ambisonic channel number 23. */ | |||
ambisonicACN24 = 50, /**< Fourth-order ambisonic channel number 24. */ | |||
// fifth-order ambisonic | |||
ambisonicACN25 = 51, /**< Fifth-order ambisonic channel number 25. */ | |||
ambisonicACN26 = 52, /**< Fifth-order ambisonic channel number 26. */ | |||
ambisonicACN27 = 53, /**< Fifth-order ambisonic channel number 27. */ | |||
ambisonicACN28 = 54, /**< Fifth-order ambisonic channel number 28. */ | |||
ambisonicACN29 = 55, /**< Fifth-order ambisonic channel number 29. */ | |||
ambisonicACN30 = 56, /**< Fifth-order ambisonic channel number 30. */ | |||
ambisonicACN31 = 57, /**< Fifth-order ambisonic channel number 31. */ | |||
ambisonicACN32 = 58, /**< Fifth-order ambisonic channel number 32. */ | |||
ambisonicACN33 = 59, /**< Fifth-order ambisonic channel number 33. */ | |||
ambisonicACN34 = 60, /**< Fifth-order ambisonic channel number 34. */ | |||
ambisonicACN35 = 61, /**< Fifth-order ambisonic channel number 35. */ | |||
//============================================================================== | |||
ambisonicW = ambisonicACN0, /**< Same as zero-th ambisonic channel number 0. */ | |||
ambisonicX = ambisonicACN3, /**< Same as first-order ambisonic channel number 3. */ | |||
ambisonicY = ambisonicACN1, /**< Same as first-order ambisonic channel number 1. */ | |||
ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */ | |||
//============================================================================== | |||
bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */ | |||
bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */ | |||
bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */ | |||
proximityLeft = 65, /**< Proximity Left (Pl) */ | |||
proximityRight = 66, /**< Proximity Right (Pr) */ | |||
bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */ | |||
bottomSideRight = 68, /**< Bottom Side Right (Bsr) */ | |||
bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */ | |||
bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ | |||
bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ | |||
//============================================================================== | |||
discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ | |||
}; | |||
/** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||
static String JUCE_CALLTYPE getChannelTypeName (ChannelType); | |||
/** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||
static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType); | |||
/** Returns the channel type from an abbreviated name. */ | |||
static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation); | |||
//============================================================================== | |||
enum | |||
{ | |||
maxChannelsOfNamedLayout = 36 | |||
}; | |||
/** Adds a channel to the set. */ | |||
void addChannel (ChannelType newChannelType); | |||
/** Removes a channel from the set. */ | |||
void removeChannel (ChannelType newChannelType); | |||
/** Returns the number of channels in the set. */ | |||
int size() const noexcept; | |||
/** Returns true if there are no channels in the set. */ | |||
bool isDisabled() const noexcept { return size() == 0; } | |||
/** Returns an array of all the types in this channel set. */ | |||
Array<ChannelType> getChannelTypes() const; | |||
/** Returns the type of one of the channels in the set, by index. */ | |||
ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||
/** Returns the index for a particular channel-type. | |||
Will return -1 if the this set does not contain a channel of this type. */ | |||
int getChannelIndexForType (ChannelType type) const noexcept; | |||
/** Returns a string containing a whitespace-separated list of speaker types | |||
corresponding to each channel. For example in a 5.1 arrangement, | |||
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||
the returned string will be empty.*/ | |||
String getSpeakerArrangementAsString() const; | |||
/** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString | |||
@see getSpeakerArrangementAsString */ | |||
static AudioChannelSet fromAbbreviatedString (const String& set); | |||
/** Returns the description of the current layout. For example, this method may return | |||
"Quadraphonic". Note that the returned string may not be unique. */ | |||
String getDescription() const; | |||
/** Returns if this is a channel layout made-up of discrete channels. */ | |||
bool isDiscreteLayout() const noexcept; | |||
/** Intersect two channel layouts. */ | |||
void intersect (const AudioChannelSet& other) { channels &= other.channels; } | |||
/** Creates a channel set for a list of channel types. This function will assert | |||
if you supply a duplicate channel. | |||
Note that this method ignores the order in which the channels are given, i.e. | |||
two arrays with the same elements but in a different order will still result | |||
in the same channel set. | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array<ChannelType>&); | |||
//============================================================================== | |||
// Conversion between wave and juce channel layout identifiers | |||
/** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used | |||
in .wav files). */ | |||
static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask); | |||
/** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav | |||
files) of the receiver. | |||
Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask | |||
representation. | |||
*/ | |||
int32 getWaveChannelMask() const noexcept; | |||
//============================================================================== | |||
bool operator== (const AudioChannelSet&) const noexcept; | |||
bool operator!= (const AudioChannelSet&) const noexcept; | |||
bool operator< (const AudioChannelSet&) const noexcept; | |||
private: | |||
//============================================================================== | |||
BigInteger channels; | |||
//============================================================================== | |||
explicit AudioChannelSet (uint32); | |||
explicit AudioChannelSet (const std::initializer_list<ChannelType>&); | |||
//============================================================================== | |||
static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int); | |||
}; | |||
} // namespace juce |
@@ -1,635 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | |||
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
*unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
*unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fffff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fffff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fffffff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
*unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fffffff; | |||
auto intData = static_cast<char*> (dest); | |||
if (dest != (void*) source || destBytesPerSample <= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
intData += destBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += destBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= destBytesPerSample; | |||
*unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||
char* d = static_cast<char*> (dest); | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<float*> (d) = source[i]; | |||
#if JUCE_BIG_ENDIAN | |||
*unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||
#endif | |||
d += destBytesPerSample; | |||
} | |||
} | |||
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||
auto d = static_cast<char*> (dest); | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
*unalignedPointerCast<float*> (d) = source[i]; | |||
#if JUCE_LITTLE_ENDIAN | |||
*unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||
#endif | |||
d += destBytesPerSample; | |||
} | |||
} | |||
//============================================================================== | |||
void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / 0x7fff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint16*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint16*> (intData)); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / 0x7fff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint16*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint16*> (intData)); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / 0x7fffff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / 0x7fffff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / (float) 0x7fffffff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
const float scale = 1.0f / (float) 0x7fffffff; | |||
auto intData = static_cast<const char*> (source); | |||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||
{ | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
intData += srcBytesPerSample; | |||
} | |||
} | |||
else | |||
{ | |||
intData += srcBytesPerSample * numSamples; | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
intData -= srcBytesPerSample; | |||
dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
} | |||
} | |||
} | |||
void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
auto s = static_cast<const char*> (source); | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = *unalignedPointerCast<const float*> (s); | |||
#if JUCE_BIG_ENDIAN | |||
auto d = unalignedPointerCast<uint32*> (dest + i); | |||
*d = ByteOrder::swap (*d); | |||
#endif | |||
s += srcBytesPerSample; | |||
} | |||
} | |||
void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) | |||
{ | |||
auto s = static_cast<const char*> (source); | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
dest[i] = *unalignedPointerCast<const float*> (s); | |||
#if JUCE_LITTLE_ENDIAN | |||
auto d = unalignedPointerCast<uint32*> (dest + i); | |||
*d = ByteOrder::swap (*d); | |||
#endif | |||
s += srcBytesPerSample; | |||
} | |||
} | |||
//============================================================================== | |||
void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, int numSamples) | |||
{ | |||
switch (destFormat) | |||
{ | |||
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; | |||
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; | |||
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; | |||
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; | |||
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; | |||
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; | |||
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; | |||
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const void* source, float* dest, int numSamples) | |||
{ | |||
switch (sourceFormat) | |||
{ | |||
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; | |||
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; | |||
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; | |||
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; | |||
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; | |||
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; | |||
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; | |||
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
//============================================================================== | |||
void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels) | |||
{ | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<Format> { source, numChannels }, | |||
AudioData::InterleavedDest<Format> { dest, numChannels }, | |||
numSamples); | |||
} | |||
void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) | |||
{ | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<Format> { source, numChannels }, | |||
AudioData::NonInterleavedDest<Format> { dest, numChannels }, | |||
numSamples); | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class AudioConversionTests : public UnitTest | |||
{ | |||
public: | |||
AudioConversionTests() | |||
: UnitTest ("Audio data conversion", UnitTestCategories::audio) | |||
{} | |||
template <class F1, class E1, class F2, class E2> | |||
struct Test5 | |||
{ | |||
static void test (UnitTest& unitTest, Random& r) | |||
{ | |||
test (unitTest, false, r); | |||
test (unitTest, true, r); | |||
} | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262) | |||
static void test (UnitTest& unitTest, bool inPlace, Random& r) | |||
{ | |||
const int numSamples = 2048; | |||
int32 original [(size_t) numSamples], | |||
converted[(size_t) numSamples], | |||
reversed [(size_t) numSamples]; | |||
{ | |||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original); | |||
bool clippingFailed = false; | |||
for (int i = 0; i < numSamples / 2; ++i) | |||
{ | |||
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); | |||
if (! d.isFloatingPoint()) | |||
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; | |||
++d; | |||
d.setAsInt32 (r.nextInt()); | |||
++d; | |||
} | |||
unitTest.expect (! clippingFailed); | |||
} | |||
// convert data from the source to dest format.. | |||
std::unique_ptr<AudioData::Converter> conv (new AudioData::ConverterInstance<AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>, | |||
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst>>()); | |||
conv->convertSamples (inPlace ? reversed : converted, original, numSamples); | |||
// ..and back again.. | |||
conv.reset (new AudioData::ConverterInstance<AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>, | |||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst>>()); | |||
if (! inPlace) | |||
zeromem (reversed, sizeof (reversed)); | |||
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); | |||
{ | |||
int biggestDiff = 0; | |||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original); | |||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed); | |||
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution() | |||
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution(); | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); | |||
++d1; | |||
++d2; | |||
} | |||
unitTest.expect (biggestDiff <= errorMargin); | |||
} | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
}; | |||
template <class F1, class E1, class FormatType> | |||
struct Test3 | |||
{ | |||
static void test (UnitTest& unitTest, Random& r) | |||
{ | |||
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest, r); | |||
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||
} | |||
}; | |||
template <class FormatType, class Endianness> | |||
struct Test2 | |||
{ | |||
static void test (UnitTest& unitTest, Random& r) | |||
{ | |||
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest, r); | |||
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest, r); | |||
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest, r); | |||
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest, r); | |||
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest, r); | |||
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest, r); | |||
} | |||
}; | |||
template <class FormatType> | |||
struct Test1 | |||
{ | |||
static void test (UnitTest& unitTest, Random& r) | |||
{ | |||
Test2 <FormatType, AudioData::BigEndian>::test (unitTest, r); | |||
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||
} | |||
}; | |||
void runTest() override | |||
{ | |||
auto r = getRandom(); | |||
beginTest ("Round-trip conversion: Int8"); | |||
Test1 <AudioData::Int8>::test (*this, r); | |||
beginTest ("Round-trip conversion: Int16"); | |||
Test1 <AudioData::Int16>::test (*this, r); | |||
beginTest ("Round-trip conversion: Int24"); | |||
Test1 <AudioData::Int24>::test (*this, r); | |||
beginTest ("Round-trip conversion: Int32"); | |||
Test1 <AudioData::Int32>::test (*this, r); | |||
beginTest ("Round-trip conversion: Float32"); | |||
Test1 <AudioData::Float32>::test (*this, r); | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
beginTest ("Interleaving"); | |||
{ | |||
constexpr auto numChannels = 4; | |||
constexpr auto numSamples = 512; | |||
AudioBuffer<float> sourceBuffer { numChannels, numSamples }, | |||
destBuffer { 1, numChannels * numSamples }; | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
sourceBuffer.setSample (ch, i, r.nextFloat()); | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<Format> { sourceBuffer.getArrayOfReadPointers(), numChannels }, | |||
AudioData::InterleavedDest<Format> { destBuffer.getWritePointer (0), numChannels }, | |||
numSamples); | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
expect (destBuffer.getSample (0, ch + (i * numChannels)) == sourceBuffer.getSample (ch, i)); | |||
} | |||
beginTest ("Deinterleaving"); | |||
{ | |||
constexpr auto numChannels = 4; | |||
constexpr auto numSamples = 512; | |||
AudioBuffer<float> sourceBuffer { 1, numChannels * numSamples }, | |||
destBuffer { numChannels, numSamples }; | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
sourceBuffer.setSample (0, ch + (i * numChannels), r.nextFloat()); | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<Format> { sourceBuffer.getReadPointer (0), numChannels }, | |||
AudioData::NonInterleavedDest<Format> { destBuffer.getArrayOfWritePointers(), numChannels }, | |||
numSamples); | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
expect (sourceBuffer.getSample (0, ch + (i * numChannels)) == destBuffer.getSample (ch, i)); | |||
} | |||
} | |||
}; | |||
static AudioConversionTests audioConversionUnitTests; | |||
#endif | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} // namespace juce |
@@ -1,857 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
This class a container which holds all the classes pertaining to the AudioData::Pointer | |||
audio sample format class. | |||
@see AudioData::Pointer. | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API AudioData | |||
{ | |||
public: | |||
//============================================================================== | |||
// These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. | |||
class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ | |||
class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ | |||
class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ | |||
class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ | |||
class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ | |||
class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ | |||
//============================================================================== | |||
// These types can be used as the Endianness template parameter for the AudioData::Pointer class. | |||
class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ | |||
class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ | |||
class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ | |||
//============================================================================== | |||
// These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. | |||
class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ | |||
class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ | |||
//============================================================================== | |||
// These types can be used as the Constness template parameter for the AudioData::Pointer class. | |||
class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ | |||
class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ | |||
#ifndef DOXYGEN | |||
//============================================================================== | |||
class BigEndian | |||
{ | |||
public: | |||
template <class SampleFormatType> static float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } | |||
template <class SampleFormatType> static void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } | |||
template <class SampleFormatType> static int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } | |||
template <class SampleFormatType> static void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } | |||
template <class SourceType, class DestType> static void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } | |||
enum { isBigEndian = 1 }; | |||
}; | |||
class LittleEndian | |||
{ | |||
public: | |||
template <class SampleFormatType> static float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } | |||
template <class SampleFormatType> static void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } | |||
template <class SampleFormatType> static int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } | |||
template <class SampleFormatType> static void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } | |||
template <class SourceType, class DestType> static void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } | |||
enum { isBigEndian = 0 }; | |||
}; | |||
#if JUCE_BIG_ENDIAN | |||
class NativeEndian : public BigEndian {}; | |||
#else | |||
class NativeEndian : public LittleEndian {}; | |||
#endif | |||
//============================================================================== | |||
class Int8 | |||
{ | |||
public: | |||
inline Int8 (void* d) noexcept : data (static_cast<int8*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + (double) maxValue))); } | |||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))); } | |||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||
inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); } | |||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||
inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } | |||
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||
inline void clear() noexcept { *data = 0; } | |||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } | |||
int8* data; | |||
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||
}; | |||
class UInt8 | |||
{ | |||
public: | |||
inline UInt8 (void* d) noexcept : data (static_cast<uint8*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + (double) maxValue))); } | |||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + (double) maxValue))); } | |||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||
inline int32 getAsInt32LE() const noexcept { return (int) (((uint8) (*data - 128)) << 24); } | |||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||
inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } | |||
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||
inline void clear() noexcept { *data = 128; } | |||
inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } | |||
uint8* data; | |||
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||
}; | |||
class Int16 | |||
{ | |||
public: | |||
inline Int16 (void* d) noexcept : data (static_cast<uint16*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } | |||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } | |||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } | |||
inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } | |||
inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } | |||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } | |||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } | |||
inline void clear() noexcept { *data = 0; } | |||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } | |||
uint16* data; | |||
enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; | |||
}; | |||
class Int24 | |||
{ | |||
public: | |||
inline Int24 (void* d) noexcept : data (static_cast<char*> (d)) {} | |||
inline void advance() noexcept { data += 3; } | |||
inline void skip (int numSamples) noexcept { data += 3 * numSamples; } | |||
inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } | |||
inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } | |||
inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } | |||
inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } | |||
inline int32 getAsInt32LE() const noexcept { return (int32) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } | |||
inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) ByteOrder::bigEndian24Bit (data)) << 8); } | |||
inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } | |||
inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } | |||
inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } | |||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } | |||
char* data; | |||
enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||
}; | |||
class Int32 | |||
{ | |||
public: | |||
inline Int32 (void* d) noexcept : data (static_cast<uint32*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } | |||
inline void clear() noexcept { *data = 0; } | |||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } | |||
uint32* data; | |||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; | |||
}; | |||
/** A 32-bit integer type, of which only the bottom 24 bits are used. */ | |||
class Int24in32 : public Int32 | |||
{ | |||
public: | |||
inline Int24in32 (void* d) noexcept : Int32 (d) {} | |||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } | |||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } | |||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } | |||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||
inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; } | |||
enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||
}; | |||
class Float32 | |||
{ | |||
public: | |||
inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
#if JUCE_BIG_ENDIAN | |||
inline float getAsFloatBE() const noexcept { return *data; } | |||
inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } | |||
inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||
inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||
#else | |||
inline float getAsFloatLE() const noexcept { return *data; } | |||
inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } | |||
inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||
inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||
#endif | |||
inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } | |||
inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } | |||
inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } | |||
inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } | |||
inline void clear() noexcept { *data = 0; } | |||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } | |||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } | |||
inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } | |||
float* data; | |||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; | |||
}; | |||
//============================================================================== | |||
class NonInterleaved | |||
{ | |||
public: | |||
inline NonInterleaved() = default; | |||
inline NonInterleaved (const NonInterleaved&) = default; | |||
inline NonInterleaved (const int) noexcept {} | |||
inline void copyFrom (const NonInterleaved&) noexcept {} | |||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } | |||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } | |||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } | |||
template <class SampleFormatType> static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||
enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||
}; | |||
class Interleaved | |||
{ | |||
public: | |||
inline Interleaved() noexcept {} | |||
inline Interleaved (const Interleaved& other) = default; | |||
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } | |||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } | |||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } | |||
template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } | |||
int numInterleavedChannels = 1; | |||
enum { isInterleavedType = 1 }; | |||
}; | |||
//============================================================================== | |||
class NonConst | |||
{ | |||
public: | |||
using VoidType = void; | |||
static void* toVoidPtr (VoidType* v) noexcept { return v; } | |||
enum { isConst = 0 }; | |||
}; | |||
class Const | |||
{ | |||
public: | |||
using VoidType = const void; | |||
static void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); } | |||
enum { isConst = 1 }; | |||
}; | |||
#endif | |||
//============================================================================== | |||
/** | |||
A pointer to a block of audio data with a particular encoding. | |||
This object can be used to read and write from blocks of encoded audio samples. To create one, you specify | |||
the audio format as a series of template parameters, e.g. | |||
@code | |||
// this creates a pointer for reading from a const array of 16-bit little-endian packed samples. | |||
AudioData::Pointer <AudioData::Int16, | |||
AudioData::LittleEndian, | |||
AudioData::NonInterleaved, | |||
AudioData::Const> pointer (someRawAudioData); | |||
// These methods read the sample that is being pointed to | |||
float firstSampleAsFloat = pointer.getAsFloat(); | |||
int32 firstSampleAsInt = pointer.getAsInt32(); | |||
++pointer; // moves the pointer to the next sample. | |||
pointer += 3; // skips the next 3 samples. | |||
@endcode | |||
The convertSamples() method lets you copy a range of samples from one format to another, automatically | |||
converting its format. | |||
@see AudioData::Converter | |||
*/ | |||
template <typename SampleFormat, | |||
typename Endianness, | |||
typename InterleavingType, | |||
typename Constness> | |||
class Pointer : private InterleavingType // (inherited for EBCO) | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a non-interleaved pointer from some raw data in the appropriate format. | |||
This constructor is only used if you've specified the AudioData::NonInterleaved option - | |||
for interleaved formats, use the constructor that also takes a number of channels. | |||
*/ | |||
Pointer (typename Constness::VoidType* sourceData) noexcept | |||
: data (Constness::toVoidPtr (sourceData)) | |||
{ | |||
// If you're using interleaved data, call the other constructor! If you're using non-interleaved data, | |||
// you should pass NonInterleaved as the template parameter for the interleaving type! | |||
static_assert (InterleavingType::isInterleavedType == 0, "Incorrect constructor for interleaved data"); | |||
} | |||
/** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. | |||
For non-interleaved data, use the other constructor. | |||
*/ | |||
Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept | |||
: InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData)) | |||
{ | |||
} | |||
/** Creates a copy of another pointer. */ | |||
Pointer (const Pointer& other) noexcept | |||
: InterleavingType (other), data (other.data) | |||
{ | |||
} | |||
Pointer& operator= (const Pointer& other) noexcept | |||
{ | |||
InterleavingType::operator= (other); | |||
data = other.data; | |||
return *this; | |||
} | |||
//============================================================================== | |||
/** Returns the value of the first sample as a floating point value. | |||
The value will be in the range -1.0 to 1.0 for integer formats. For floating point | |||
formats, the value could be outside that range, although -1 to 1 is the standard range. | |||
*/ | |||
inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); } | |||
/** Sets the value of the first sample as a floating point value. | |||
(This method can only be used if the AudioData::NonConst option was used). | |||
The value should be in the range -1.0 to 1.0 - for integer formats, values outside that | |||
range will be clipped. For floating point formats, any value passed in here will be | |||
written directly, although -1 to 1 is the standard range. | |||
*/ | |||
inline void setAsFloat (float newValue) noexcept | |||
{ | |||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||
Endianness::setAsFloat (data, newValue); | |||
} | |||
/** Returns the value of the first sample as a 32-bit integer. | |||
The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be | |||
shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up | |||
by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will | |||
be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff. | |||
*/ | |||
inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); } | |||
/** Sets the value of the first sample as a 32-bit integer. | |||
This will be mapped to the range of the format that is being written - see getAsInt32(). | |||
*/ | |||
inline void setAsInt32 (int32 newValue) noexcept | |||
{ | |||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||
Endianness::setAsInt32 (data, newValue); | |||
} | |||
/** Moves the pointer along to the next sample. */ | |||
inline Pointer& operator++() noexcept { advance(); return *this; } | |||
/** Moves the pointer back to the previous sample. */ | |||
inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; } | |||
/** Adds a number of samples to the pointer's position. */ | |||
Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } | |||
/** Writes a stream of samples into this pointer from another pointer. | |||
This will copy the specified number of samples, converting between formats appropriately. | |||
*/ | |||
void convertSamples (Pointer source, int numSamples) const noexcept | |||
{ | |||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||
for (Pointer dest (*this); --numSamples >= 0;) | |||
{ | |||
dest.data.copyFromSameType (source.data); | |||
dest.advance(); | |||
source.advance(); | |||
} | |||
} | |||
/** Writes a stream of samples into this pointer from another pointer. | |||
This will copy the specified number of samples, converting between formats appropriately. | |||
*/ | |||
template <class OtherPointerType> | |||
void convertSamples (OtherPointerType source, int numSamples) const noexcept | |||
{ | |||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||
Pointer dest (*this); | |||
if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples()) | |||
{ | |||
while (--numSamples >= 0) | |||
{ | |||
Endianness::copyFrom (dest.data, source); | |||
dest.advance(); | |||
++source; | |||
} | |||
} | |||
else // copy backwards if we're increasing the sample width.. | |||
{ | |||
dest += numSamples; | |||
source += numSamples; | |||
while (--numSamples >= 0) | |||
Endianness::copyFrom ((--dest).data, --source); | |||
} | |||
} | |||
/** Sets a number of samples to zero. */ | |||
void clearSamples (int numSamples) const noexcept | |||
{ | |||
Pointer dest (*this); | |||
dest.clear (dest.data, numSamples); | |||
} | |||
/** Scans a block of data, returning the lowest and highest levels as floats */ | |||
Range<float> findMinAndMax (size_t numSamples) const noexcept | |||
{ | |||
if (numSamples == 0) | |||
return Range<float>(); | |||
Pointer dest (*this); | |||
if (isFloatingPoint()) | |||
{ | |||
float mn = dest.getAsFloat(); | |||
dest.advance(); | |||
float mx = mn; | |||
while (--numSamples > 0) | |||
{ | |||
const float v = dest.getAsFloat(); | |||
dest.advance(); | |||
if (mx < v) mx = v; | |||
if (v < mn) mn = v; | |||
} | |||
return Range<float> (mn, mx); | |||
} | |||
int32 mn = dest.getAsInt32(); | |||
dest.advance(); | |||
int32 mx = mn; | |||
while (--numSamples > 0) | |||
{ | |||
const int v = dest.getAsInt32(); | |||
dest.advance(); | |||
if (mx < v) mx = v; | |||
if (v < mn) mn = v; | |||
} | |||
return Range<float> ((float) mn * (float) (1.0 / (1.0 + (double) Int32::maxValue)), | |||
(float) mx * (float) (1.0 / (1.0 + (double) Int32::maxValue))); | |||
} | |||
/** Scans a block of data, returning the lowest and highest levels as floats */ | |||
void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept | |||
{ | |||
Range<float> r (findMinAndMax (numSamples)); | |||
minValue = r.getStart(); | |||
maxValue = r.getEnd(); | |||
} | |||
/** Returns true if the pointer is using a floating-point format. */ | |||
static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } | |||
/** Returns true if the format is big-endian. */ | |||
static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } | |||
/** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ | |||
static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } | |||
/** Returns the number of interleaved channels in the format. */ | |||
int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } | |||
/** Returns the number of bytes between the start address of each sample. */ | |||
int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } | |||
/** Returns the accuracy of this format when represented as a 32-bit integer. | |||
This is the smallest number above 0 that can be represented in the sample format, converted to | |||
a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, | |||
its resolution is 0x100. | |||
*/ | |||
static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } | |||
/** Returns a pointer to the underlying data. */ | |||
const void* getRawData() const noexcept { return data.data; } | |||
private: | |||
//============================================================================== | |||
SampleFormat data; | |||
inline void advance() noexcept { this->advanceData (data); } | |||
Pointer operator++ (int); // private to force you to use the more efficient pre-increment! | |||
Pointer operator-- (int); | |||
}; | |||
//============================================================================== | |||
/** A base class for objects that are used to convert between two different sample formats. | |||
The AudioData::ConverterInstance implements this base class and can be templated, so | |||
you can create an instance that converts between two particular formats, and then | |||
store this in the abstract base class. | |||
@see AudioData::ConverterInstance | |||
*/ | |||
class Converter | |||
{ | |||
public: | |||
virtual ~Converter() = default; | |||
/** Converts a sequence of samples from the converter's source format into the dest format. */ | |||
virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||
/** Converts a sequence of samples from the converter's source format into the dest format. | |||
This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a | |||
particular sub-channel of the data to be used. | |||
*/ | |||
virtual void convertSamples (void* destSamples, int destSubChannel, | |||
const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; | |||
}; | |||
//============================================================================== | |||
/** | |||
A class that converts between two templated AudioData::Pointer types, and which | |||
implements the AudioData::Converter interface. | |||
This can be used as a concrete instance of the AudioData::Converter abstract class. | |||
@see AudioData::Converter | |||
*/ | |||
template <class SourceSampleType, class DestSampleType> | |||
class ConverterInstance : public Converter | |||
{ | |||
public: | |||
ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) | |||
: sourceChannels (numSourceChannels), destChannels (numDestChannels) | |||
{} | |||
void convertSamples (void* dest, const void* source, int numSamples) const override | |||
{ | |||
SourceSampleType s (source, sourceChannels); | |||
DestSampleType d (dest, destChannels); | |||
d.convertSamples (s, numSamples); | |||
} | |||
void convertSamples (void* dest, int destSubChannel, | |||
const void* source, int sourceSubChannel, int numSamples) const override | |||
{ | |||
jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); | |||
SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); | |||
DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); | |||
d.convertSamples (s, numSamples); | |||
} | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE (ConverterInstance) | |||
const int sourceChannels, destChannels; | |||
}; | |||
//============================================================================== | |||
/** A struct that contains a SampleFormat and Endianness to be used with the source and | |||
destination types when calling the interleaveSamples() and deinterleaveSamples() helpers. | |||
@see interleaveSamples, deinterleaveSamples | |||
*/ | |||
template <typename DataFormatIn, typename EndiannessIn> | |||
struct Format | |||
{ | |||
using DataFormat = DataFormatIn; | |||
using Endianness = EndiannessIn; | |||
}; | |||
private: | |||
template <bool IsInterleaved, bool IsConst, typename...> | |||
struct ChannelDataSubtypes; | |||
template <bool IsInterleaved, bool IsConst, typename DataFormat, typename Endianness> | |||
struct ChannelDataSubtypes<IsInterleaved, IsConst, DataFormat, Endianness> | |||
{ | |||
using ElementType = std::remove_pointer_t<decltype (DataFormat::data)>; | |||
using ChannelType = std::conditional_t<IsConst, const ElementType*, ElementType*>; | |||
using DataType = std::conditional_t<IsInterleaved, ChannelType, ChannelType*>; | |||
using PointerType = Pointer<DataFormat, | |||
Endianness, | |||
std::conditional_t<IsInterleaved, Interleaved, NonInterleaved>, | |||
std::conditional_t<IsConst, Const, NonConst>>; | |||
}; | |||
template <bool IsInterleaved, bool IsConst, typename DataFormat, typename Endianness> | |||
struct ChannelDataSubtypes<IsInterleaved, IsConst, Format<DataFormat, Endianness>> | |||
{ | |||
using Subtypes = ChannelDataSubtypes<IsInterleaved, IsConst, DataFormat, Endianness>; | |||
using DataType = typename Subtypes::DataType; | |||
using PointerType = typename Subtypes::PointerType; | |||
}; | |||
template <bool IsInterleaved, bool IsConst, typename... Format> | |||
struct ChannelData | |||
{ | |||
using Subtypes = ChannelDataSubtypes<IsInterleaved, IsConst, Format...>; | |||
using DataType = typename Subtypes::DataType; | |||
using PointerType = typename Subtypes::PointerType; | |||
DataType data; | |||
int channels; | |||
}; | |||
public: | |||
//============================================================================== | |||
/** A sequence of interleaved samples used as the source for the deinterleaveSamples() method. */ | |||
template <typename... Format> using InterleavedSource = ChannelData<true, true, Format...>; | |||
/** A sequence of interleaved samples used as the destination for the interleaveSamples() method. */ | |||
template <typename... Format> using InterleavedDest = ChannelData<true, false, Format...>; | |||
/** A sequence of non-interleaved samples used as the source for the interleaveSamples() method. */ | |||
template <typename... Format> using NonInterleavedSource = ChannelData<false, true, Format...>; | |||
/** A sequence of non-interleaved samples used as the destination for the deinterleaveSamples() method. */ | |||
template <typename... Format> using NonInterleavedDest = ChannelData<false, false, Format...>; | |||
/** A helper function for converting a sequence of samples from a non-interleaved source | |||
to an interleaved destination. | |||
When calling this method you need to specify the source and destination data format and endianness | |||
from the AudioData SampleFormat and Endianness types and provide the data and number of channels | |||
for each. For example, to convert a floating-point stream of big endian samples to an interleaved, | |||
native endian stream of 16-bit integer samples you would do the following: | |||
@code | |||
using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::BigEndian>; | |||
using DestFormat = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>; | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<SourceFormat> { sourceData, numSourceChannels }, | |||
AudioData::InterleavedDest<DestFormat> { destData, numDestChannels }, | |||
numSamples); | |||
@endcode | |||
*/ | |||
template <typename... SourceFormat, typename... DestFormat> | |||
static void interleaveSamples (NonInterleavedSource<SourceFormat...> source, | |||
InterleavedDest<DestFormat...> dest, | |||
int numSamples) | |||
{ | |||
using SourceType = typename decltype (source)::PointerType; | |||
using DestType = typename decltype (dest) ::PointerType; | |||
for (int i = 0; i < dest.channels; ++i) | |||
{ | |||
const DestType destType (addBytesToPointer (dest.data, i * DestType::getBytesPerSample()), dest.channels); | |||
if (i < source.channels) | |||
{ | |||
if (*source.data != nullptr) | |||
{ | |||
destType.convertSamples (SourceType { *source.data }, numSamples); | |||
++source.data; | |||
} | |||
} | |||
else | |||
{ | |||
destType.clearSamples (numSamples); | |||
} | |||
} | |||
} | |||
/** A helper function for converting a sequence of samples from an interleaved source | |||
to a non-interleaved destination. | |||
When calling this method you need to specify the source and destination data format and endianness | |||
from the AudioData SampleFormat and Endianness types and provide the data and number of channels | |||
for each. For example, to convert a floating-point stream of big endian samples to an non-interleaved, | |||
native endian stream of 16-bit integer samples you would do the following: | |||
@code | |||
using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::BigEndian>; | |||
using DestFormat = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>; | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<SourceFormat> { sourceData, numSourceChannels }, | |||
AudioData::NonInterleavedDest<DestFormat> { destData, numDestChannels }, | |||
numSamples); | |||
@endcode | |||
*/ | |||
template <typename... SourceFormat, typename... DestFormat> | |||
static void deinterleaveSamples (InterleavedSource<SourceFormat...> source, | |||
NonInterleavedDest<DestFormat...> dest, | |||
int numSamples) | |||
{ | |||
using SourceType = typename decltype (source)::PointerType; | |||
using DestType = typename decltype (dest) ::PointerType; | |||
for (int i = 0; i < dest.channels; ++i) | |||
{ | |||
if (auto* targetChan = dest.data[i]) | |||
{ | |||
const DestType destType (targetChan); | |||
if (i < source.channels) | |||
destType.convertSamples (SourceType (addBytesToPointer (source.data, i * SourceType::getBytesPerSample()), source.channels), numSamples); | |||
else | |||
destType.clearSamples (numSamples); | |||
} | |||
} | |||
} | |||
}; | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
/** | |||
A set of routines to convert buffers of 32-bit floating point data to and from | |||
various integer formats. | |||
Note that these functions are deprecated - the AudioData class provides a much more | |||
flexible set of conversion classes now. | |||
@tags{Audio} | |||
*/ | |||
class [[deprecated]] JUCE_API AudioDataConverters | |||
{ | |||
public: | |||
//============================================================================== | |||
static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||
static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||
static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||
static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||
static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||
static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||
static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||
static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||
//============================================================================== | |||
static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||
static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||
static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||
static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||
static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||
static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||
static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||
static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||
//============================================================================== | |||
enum DataFormat | |||
{ | |||
int16LE, | |||
int16BE, | |||
int24LE, | |||
int24BE, | |||
int32LE, | |||
int32BE, | |||
float32LE, | |||
float32BE, | |||
}; | |||
static void convertFloatToFormat (DataFormat destFormat, | |||
const float* source, void* dest, int numSamples); | |||
static void convertFormatToFloat (DataFormat sourceFormat, | |||
const void* source, float* dest, int numSamples); | |||
//============================================================================== | |||
static void interleaveSamples (const float** source, float* dest, | |||
int numSamples, int numChannels); | |||
static void deinterleaveSamples (const float* source, float** dest, | |||
int numSamples, int numChannels); | |||
private: | |||
AudioDataConverters(); | |||
}; | |||
#endif | |||
} // namespace juce |
@@ -1,99 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() = default; | |||
AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() = default; | |||
void AudioProcessLoadMeasurer::reset() | |||
{ | |||
reset (0, 0); | |||
} | |||
void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) | |||
{ | |||
const SpinLock::ScopedLockType lock (mutex); | |||
cpuUsageProportion = 0; | |||
xruns = 0; | |||
samplesPerBlock = blockSize; | |||
msPerSample = (sampleRate > 0.0 && blockSize > 0) ? 1000.0 / sampleRate : 0; | |||
} | |||
void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) | |||
{ | |||
const SpinLock::ScopedTryLockType lock (mutex); | |||
if (lock.isLocked()) | |||
registerRenderTimeLocked (milliseconds, samplesPerBlock); | |||
} | |||
void AudioProcessLoadMeasurer::registerRenderTime (double milliseconds, int numSamples) | |||
{ | |||
const SpinLock::ScopedTryLockType lock (mutex); | |||
if (lock.isLocked()) | |||
registerRenderTimeLocked (milliseconds, numSamples); | |||
} | |||
void AudioProcessLoadMeasurer::registerRenderTimeLocked (double milliseconds, int numSamples) | |||
{ | |||
if (msPerSample == 0) | |||
return; | |||
const auto maxMilliseconds = numSamples * msPerSample; | |||
const auto usedProportion = milliseconds / maxMilliseconds; | |||
const auto filterAmount = 0.2; | |||
const auto proportion = cpuUsageProportion.load(); | |||
cpuUsageProportion = proportion + filterAmount * (usedProportion - proportion); | |||
if (milliseconds > maxMilliseconds) | |||
++xruns; | |||
} | |||
double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, cpuUsageProportion.load()); } | |||
double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } | |||
int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } | |||
AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) | |||
: ScopedTimer (p, p.samplesPerBlock) | |||
{ | |||
} | |||
AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p, int numSamplesInBlock) | |||
: owner (p), startTime (Time::getMillisecondCounterHiRes()), samplesInBlock (numSamplesInBlock) | |||
{ | |||
// numSamplesInBlock should never be zero. Did you remember to call AudioProcessLoadMeasurer::reset(), | |||
// passing the expected samples per block? | |||
jassert (numSamplesInBlock); | |||
} | |||
AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() | |||
{ | |||
owner.registerRenderTime (Time::getMillisecondCounterHiRes() - startTime, samplesInBlock); | |||
} | |||
} // namespace juce |
@@ -1,109 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Maintains an ongoing measurement of the proportion of time which is being | |||
spent inside an audio callback. | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API AudioProcessLoadMeasurer | |||
{ | |||
public: | |||
/** */ | |||
AudioProcessLoadMeasurer(); | |||
/** Destructor. */ | |||
~AudioProcessLoadMeasurer(); | |||
//============================================================================== | |||
/** Resets the state. */ | |||
void reset(); | |||
/** Resets the counter, in preparation for use with the given sample rate and block size. */ | |||
void reset (double sampleRate, int blockSize); | |||
/** Returns the current load as a proportion 0 to 1.0 */ | |||
double getLoadAsProportion() const; | |||
/** Returns the current load as a percentage 0 to 100.0 */ | |||
double getLoadAsPercentage() const; | |||
/** Returns the number of over- (or under-) runs recorded since the state was reset. */ | |||
int getXRunCount() const; | |||
//============================================================================== | |||
/** This class measures the time between its construction and destruction and | |||
adds it to an AudioProcessLoadMeasurer. | |||
e.g. | |||
@code | |||
{ | |||
AudioProcessLoadMeasurer::ScopedTimer timer (myProcessLoadMeasurer); | |||
myCallback->doTheCallback(); | |||
} | |||
@endcode | |||
@tags{Audio} | |||
*/ | |||
struct JUCE_API ScopedTimer | |||
{ | |||
ScopedTimer (AudioProcessLoadMeasurer&); | |||
ScopedTimer (AudioProcessLoadMeasurer&, int numSamplesInBlock); | |||
~ScopedTimer(); | |||
private: | |||
AudioProcessLoadMeasurer& owner; | |||
double startTime; | |||
int samplesInBlock; | |||
JUCE_DECLARE_NON_COPYABLE (ScopedTimer) | |||
}; | |||
/** Can be called manually to add the time of a callback to the stats. | |||
Normally you probably would never call this - it's simpler and more robust to | |||
use a ScopedTimer to measure the time using an RAII pattern. | |||
*/ | |||
void registerBlockRenderTime (double millisecondsTaken); | |||
/** Can be called manually to add the time of a callback to the stats. | |||
Normally you probably would never call this - it's simpler and more robust to | |||
use a ScopedTimer to measure the time using an RAII pattern. | |||
*/ | |||
void registerRenderTime (double millisecondsTaken, int numSamples); | |||
private: | |||
void registerRenderTimeLocked (double, int); | |||
SpinLock mutex; | |||
int samplesPerBlock = 0; | |||
double msPerSample = 0; | |||
std::atomic<double> cpuUsageProportion { 0 }; | |||
std::atomic<int> xruns { 0 }; | |||
}; | |||
} // namespace juce |
@@ -1,269 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
#ifndef JUCE_SNAP_TO_ZERO | |||
#if JUCE_INTEL | |||
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; | |||
#else | |||
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) | |||
#endif | |||
#endif | |||
class ScopedNoDenormals; | |||
//============================================================================== | |||
/** | |||
A collection of simple vector operations on arrays of floating point numbers, | |||
accelerated with SIMD instructions where possible, usually accessed from | |||
the FloatVectorOperations class. | |||
@code | |||
float data[64]; | |||
// The following two function calls are equivalent: | |||
FloatVectorOperationsBase<float, int>::clear (data, 64); | |||
FloatVectorOperations::clear (data, 64); | |||
@endcode | |||
@see FloatVectorOperations | |||
@tags{Audio} | |||
*/ | |||
template <typename FloatType, typename CountType> | |||
struct FloatVectorOperationsBase | |||
{ | |||
/** Clears a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE clear (FloatType* dest, CountType numValues) noexcept; | |||
/** Copies a repeated value into a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE fill (FloatType* dest, FloatType valueToFill, CountType numValues) noexcept; | |||
/** Copies a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE copy (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Copies a vector of floating point numbers, multiplying each value by a given multiplier */ | |||
static void JUCE_CALLTYPE copyWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept; | |||
/** Adds a fixed value to the destination values. */ | |||
static void JUCE_CALLTYPE add (FloatType* dest, FloatType amountToAdd, CountType numValues) noexcept; | |||
/** Adds a fixed value to each source value and stores it in the destination array. */ | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, FloatType amount, CountType numValues) noexcept; | |||
/** Adds the source values to the destination values. */ | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Subtracts the source values from the destination values. */ | |||
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept; | |||
/** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ | |||
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ | |||
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept; | |||
/** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ | |||
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Multiplies the destination values by the source values. */ | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType numValues) noexcept; | |||
/** Multiplies each of the destination values by a fixed multiplier. */ | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, FloatType multiplier, CountType numValues) noexcept; | |||
/** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType num) noexcept; | |||
/** Copies a source vector to a destination, negating each value. */ | |||
static void JUCE_CALLTYPE negate (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Copies a source vector to a destination, taking the absolute of each value. */ | |||
static void JUCE_CALLTYPE abs (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ | |||
static void JUCE_CALLTYPE min (FloatType* dest, const FloatType* src, FloatType comp, CountType num) noexcept; | |||
/** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ | |||
static void JUCE_CALLTYPE min (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ | |||
static void JUCE_CALLTYPE max (FloatType* dest, const FloatType* src, FloatType comp, CountType num) noexcept; | |||
/** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ | |||
static void JUCE_CALLTYPE max (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ | |||
static void JUCE_CALLTYPE clip (FloatType* dest, const FloatType* src, FloatType low, FloatType high, CountType num) noexcept; | |||
/** Finds the minimum and maximum values in the given array. */ | |||
static Range<FloatType> JUCE_CALLTYPE findMinAndMax (const FloatType* src, CountType numValues) noexcept; | |||
/** Finds the minimum value in the given array. */ | |||
static FloatType JUCE_CALLTYPE findMinimum (const FloatType* src, CountType numValues) noexcept; | |||
/** Finds the maximum value in the given array. */ | |||
static FloatType JUCE_CALLTYPE findMaximum (const FloatType* src, CountType numValues) noexcept; | |||
}; | |||
#if ! DOXYGEN | |||
namespace detail | |||
{ | |||
template <typename...> | |||
struct NameForwarder; | |||
template <typename Head> | |||
struct NameForwarder<Head> : Head {}; | |||
template <typename Head, typename... Tail> | |||
struct NameForwarder<Head, Tail...> : Head, NameForwarder<Tail...> | |||
{ | |||
using Head::clear; | |||
using NameForwarder<Tail...>::clear; | |||
using Head::fill; | |||
using NameForwarder<Tail...>::fill; | |||
using Head::copy; | |||
using NameForwarder<Tail...>::copy; | |||
using Head::copyWithMultiply; | |||
using NameForwarder<Tail...>::copyWithMultiply; | |||
using Head::add; | |||
using NameForwarder<Tail...>::add; | |||
using Head::subtract; | |||
using NameForwarder<Tail...>::subtract; | |||
using Head::addWithMultiply; | |||
using NameForwarder<Tail...>::addWithMultiply; | |||
using Head::subtractWithMultiply; | |||
using NameForwarder<Tail...>::subtractWithMultiply; | |||
using Head::multiply; | |||
using NameForwarder<Tail...>::multiply; | |||
using Head::negate; | |||
using NameForwarder<Tail...>::negate; | |||
using Head::abs; | |||
using NameForwarder<Tail...>::abs; | |||
using Head::min; | |||
using NameForwarder<Tail...>::min; | |||
using Head::max; | |||
using NameForwarder<Tail...>::max; | |||
using Head::clip; | |||
using NameForwarder<Tail...>::clip; | |||
using Head::findMinAndMax; | |||
using NameForwarder<Tail...>::findMinAndMax; | |||
using Head::findMinimum; | |||
using NameForwarder<Tail...>::findMinimum; | |||
using Head::findMaximum; | |||
using NameForwarder<Tail...>::findMaximum; | |||
}; | |||
} // namespace detail | |||
#endif | |||
//============================================================================== | |||
/** | |||
A collection of simple vector operations on arrays of floating point numbers, | |||
accelerated with SIMD instructions where possible and providing all methods | |||
from FloatVectorOperationsBase. | |||
@see FloatVectorOperationsBase | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API FloatVectorOperations : public detail::NameForwarder<FloatVectorOperationsBase<float, int>, | |||
FloatVectorOperationsBase<float, size_t>, | |||
FloatVectorOperationsBase<double, int>, | |||
FloatVectorOperationsBase<double, size_t>> | |||
{ | |||
public: | |||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept; | |||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, size_t num) noexcept; | |||
/** This method enables or disables the SSE/NEON flush-to-zero mode. */ | |||
static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; | |||
/** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. | |||
This effectively sets the DAZ and FZ bits of the MXCSR register. On arm CPUs this will | |||
enable flush to zero mode. | |||
It's a convenient thing to call before audio processing code where you really want to | |||
avoid denormalisation performance hits. | |||
*/ | |||
static void JUCE_CALLTYPE disableDenormalisedNumberSupport (bool shouldDisable = true) noexcept; | |||
/** This method returns true if denormals are currently disabled. */ | |||
static bool JUCE_CALLTYPE areDenormalsDisabled() noexcept; | |||
private: | |||
friend ScopedNoDenormals; | |||
static intptr_t JUCE_CALLTYPE getFpStatusRegister() noexcept; | |||
static void JUCE_CALLTYPE setFpStatusRegister (intptr_t) noexcept; | |||
}; | |||
//============================================================================== | |||
/** | |||
Helper class providing an RAII-based mechanism for temporarily disabling | |||
denormals on your CPU. | |||
@tags{Audio} | |||
*/ | |||
class ScopedNoDenormals | |||
{ | |||
public: | |||
ScopedNoDenormals() noexcept; | |||
~ScopedNoDenormals() noexcept; | |||
private: | |||
#if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) | |||
intptr_t fpsr; | |||
#endif | |||
}; | |||
} // namespace juce |
@@ -1,99 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifdef JUCE_AUDIO_BASICS_H_INCLUDED | |||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
already included any other headers - just put it inside a file on its own, possibly with your config | |||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
header files that the compiler may be using. | |||
*/ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "juce_audio_basics.h" | |||
#if JUCE_MINGW && ! defined (alloca) | |||
#define alloca __builtin_alloca | |||
#endif | |||
#if JUCE_USE_SSE_INTRINSICS | |||
#include <emmintrin.h> | |||
#endif | |||
#ifndef JUCE_USE_VDSP_FRAMEWORK | |||
#define JUCE_USE_VDSP_FRAMEWORK 1 | |||
#endif | |||
#if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK | |||
#include <Accelerate/Accelerate.h> | |||
#else | |||
#undef JUCE_USE_VDSP_FRAMEWORK | |||
#endif | |||
#if JUCE_USE_ARM_NEON | |||
#include <arm_neon.h> | |||
#endif | |||
#include "buffers/juce_AudioDataConverters.cpp" | |||
#include "buffers/juce_FloatVectorOperations.cpp" | |||
#include "buffers/juce_AudioChannelSet.cpp" | |||
#include "buffers/juce_AudioProcessLoadMeasurer.cpp" | |||
#include "utilities/juce_IIRFilter.cpp" | |||
#include "utilities/juce_LagrangeInterpolator.cpp" | |||
#include "utilities/juce_WindowedSincInterpolator.cpp" | |||
#include "utilities/juce_Interpolators.cpp" | |||
#include "utilities/juce_SmoothedValue.cpp" | |||
#include "midi/juce_MidiBuffer.cpp" | |||
#include "midi/juce_MidiFile.cpp" | |||
#include "midi/juce_MidiKeyboardState.cpp" | |||
#include "midi/juce_MidiMessage.cpp" | |||
#include "midi/juce_MidiMessageSequence.cpp" | |||
#include "midi/juce_MidiRPN.cpp" | |||
#include "mpe/juce_MPEValue.cpp" | |||
#include "mpe/juce_MPENote.cpp" | |||
#include "mpe/juce_MPEZoneLayout.cpp" | |||
#include "mpe/juce_MPEInstrument.cpp" | |||
#include "mpe/juce_MPEMessages.cpp" | |||
#include "mpe/juce_MPESynthesiserBase.cpp" | |||
#include "mpe/juce_MPESynthesiserVoice.cpp" | |||
#include "mpe/juce_MPESynthesiser.cpp" | |||
#include "mpe/juce_MPEUtils.cpp" | |||
#include "sources/juce_BufferingAudioSource.cpp" | |||
#include "sources/juce_ChannelRemappingAudioSource.cpp" | |||
#include "sources/juce_IIRFilterAudioSource.cpp" | |||
#include "sources/juce_MemoryAudioSource.cpp" | |||
#include "sources/juce_MixerAudioSource.cpp" | |||
#include "sources/juce_ResamplingAudioSource.cpp" | |||
#include "sources/juce_ReverbAudioSource.cpp" | |||
#include "sources/juce_ToneGeneratorAudioSource.cpp" | |||
#include "synthesisers/juce_Synthesiser.cpp" | |||
#include "midi/ump/juce_UMP.h" | |||
#include "midi/ump/juce_UMPUtils.cpp" | |||
#include "midi/ump/juce_UMPView.cpp" | |||
#include "midi/ump/juce_UMPSysEx7.cpp" | |||
#include "midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" | |||
#if JUCE_UNIT_TESTS | |||
#include "utilities/juce_ADSR_test.cpp" | |||
#include "midi/ump/juce_UMP_test.cpp" | |||
#endif |
@@ -1,123 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
/******************************************************************************* | |||
The block below describes the properties of this module, and is read by | |||
the Projucer to automatically generate project code that uses it. | |||
For details about the syntax and how to create or use a module, see the | |||
JUCE Module Format.md file. | |||
BEGIN_JUCE_MODULE_DECLARATION | |||
ID: juce_audio_basics | |||
vendor: juce | |||
version: 7.0.1 | |||
name: JUCE audio and MIDI data classes | |||
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||
website: http://www.juce.com/juce | |||
license: ISC | |||
minimumCppStandard: 14 | |||
dependencies: juce_core | |||
OSXFrameworks: Accelerate | |||
iOSFrameworks: Accelerate | |||
END_JUCE_MODULE_DECLARATION | |||
*******************************************************************************/ | |||
#pragma once | |||
#define JUCE_AUDIO_BASICS_H_INCLUDED | |||
#include <juce_core/juce_core.h> | |||
//============================================================================== | |||
#undef Complex // apparently some C libraries actually define these symbols (!) | |||
#undef Factor | |||
//============================================================================== | |||
#if JUCE_MINGW && ! defined (__SSE2__) | |||
#define JUCE_USE_SSE_INTRINSICS 0 | |||
#endif | |||
#ifndef JUCE_USE_SSE_INTRINSICS | |||
#define JUCE_USE_SSE_INTRINSICS 1 | |||
#endif | |||
#if ! JUCE_INTEL | |||
#undef JUCE_USE_SSE_INTRINSICS | |||
#endif | |||
#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) | |||
#define JUCE_USE_ARM_NEON 1 | |||
#endif | |||
#if TARGET_IPHONE_SIMULATOR | |||
#ifdef JUCE_USE_ARM_NEON | |||
#undef JUCE_USE_ARM_NEON | |||
#endif | |||
#define JUCE_USE_ARM_NEON 0 | |||
#endif | |||
//============================================================================== | |||
#include "buffers/juce_AudioDataConverters.h" | |||
#include "buffers/juce_FloatVectorOperations.h" | |||
#include "buffers/juce_AudioSampleBuffer.h" | |||
#include "buffers/juce_AudioChannelSet.h" | |||
#include "buffers/juce_AudioProcessLoadMeasurer.h" | |||
#include "utilities/juce_Decibels.h" | |||
#include "utilities/juce_IIRFilter.h" | |||
#include "utilities/juce_GenericInterpolator.h" | |||
#include "utilities/juce_Interpolators.h" | |||
#include "utilities/juce_SmoothedValue.h" | |||
#include "utilities/juce_Reverb.h" | |||
#include "utilities/juce_ADSR.h" | |||
#include "midi/juce_MidiMessage.h" | |||
#include "midi/juce_MidiBuffer.h" | |||
#include "midi/juce_MidiMessageSequence.h" | |||
#include "midi/juce_MidiFile.h" | |||
#include "midi/juce_MidiKeyboardState.h" | |||
#include "midi/juce_MidiRPN.h" | |||
#include "mpe/juce_MPEValue.h" | |||
#include "mpe/juce_MPENote.h" | |||
#include "mpe/juce_MPEZoneLayout.h" | |||
#include "mpe/juce_MPEInstrument.h" | |||
#include "mpe/juce_MPEMessages.h" | |||
#include "mpe/juce_MPESynthesiserBase.h" | |||
#include "mpe/juce_MPESynthesiserVoice.h" | |||
#include "mpe/juce_MPESynthesiser.h" | |||
#include "mpe/juce_MPEUtils.h" | |||
#include "sources/juce_AudioSource.h" | |||
#include "sources/juce_PositionableAudioSource.h" | |||
#include "sources/juce_BufferingAudioSource.h" | |||
#include "sources/juce_ChannelRemappingAudioSource.h" | |||
#include "sources/juce_IIRFilterAudioSource.h" | |||
#include "sources/juce_MemoryAudioSource.h" | |||
#include "sources/juce_MixerAudioSource.h" | |||
#include "sources/juce_ResamplingAudioSource.h" | |||
#include "sources/juce_ReverbAudioSource.h" | |||
#include "sources/juce_ToneGeneratorAudioSource.h" | |||
#include "synthesisers/juce_Synthesiser.h" | |||
#include "audio_play_head/juce_AudioPlayHead.h" |
@@ -1,320 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace MidiBufferHelpers | |||
{ | |||
inline int getEventTime (const void* d) noexcept | |||
{ | |||
return readUnaligned<int32> (d); | |||
} | |||
inline uint16 getEventDataSize (const void* d) noexcept | |||
{ | |||
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32)); | |||
} | |||
inline uint16 getEventTotalSize (const void* d) noexcept | |||
{ | |||
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); | |||
} | |||
static int findActualEventLength (const uint8* data, int maxBytes) noexcept | |||
{ | |||
auto byte = (unsigned int) *data; | |||
if (byte == 0xf0 || byte == 0xf7) | |||
{ | |||
int i = 1; | |||
while (i < maxBytes) | |||
if (data[i++] == 0xf7) | |||
break; | |||
return i; | |||
} | |||
if (byte == 0xff) | |||
{ | |||
if (maxBytes == 1) | |||
return 1; | |||
const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1); | |||
return jmin (maxBytes, var.value + 2 + var.bytesUsed); | |||
} | |||
if (byte >= 0x80) | |||
return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); | |||
return 0; | |||
} | |||
static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept | |||
{ | |||
while (d < endData && getEventTime (d) <= samplePosition) | |||
d += getEventTotalSize (d); | |||
return d; | |||
} | |||
} | |||
//============================================================================== | |||
MidiBufferIterator& MidiBufferIterator::operator++() noexcept | |||
{ | |||
data += sizeof (int32) + sizeof (uint16) + size_t (MidiBufferHelpers::getEventDataSize (data)); | |||
return *this; | |||
} | |||
MidiBufferIterator MidiBufferIterator::operator++ (int) noexcept | |||
{ | |||
auto copy = *this; | |||
++(*this); | |||
return copy; | |||
} | |||
MidiBufferIterator::reference MidiBufferIterator::operator*() const noexcept | |||
{ | |||
return { data + sizeof (int32) + sizeof (uint16), | |||
MidiBufferHelpers::getEventDataSize (data), | |||
MidiBufferHelpers::getEventTime (data) }; | |||
} | |||
//============================================================================== | |||
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept | |||
{ | |||
addEvent (message, 0); | |||
} | |||
void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } | |||
void MidiBuffer::clear() noexcept { data.clearQuick(); } | |||
void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } | |||
bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } | |||
void MidiBuffer::clear (int startSample, int numSamples) | |||
{ | |||
auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | |||
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); | |||
data.removeRange ((int) (start - data.begin()), (int) (end - start)); | |||
} | |||
bool MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | |||
{ | |||
return addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||
} | |||
bool MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) | |||
{ | |||
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes); | |||
if (numBytes <= 0) | |||
return true; | |||
if (std::numeric_limits<uint16>::max() < numBytes) | |||
{ | |||
// This method only supports messages smaller than (1 << 16) bytes | |||
return false; | |||
} | |||
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||
data.insertMultiple (offset, 0, (int) newItemSize); | |||
auto* d = data.begin() + offset; | |||
writeUnaligned<int32> (d, sampleNumber); | |||
d += sizeof (int32); | |||
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes)); | |||
d += sizeof (uint16); | |||
memcpy (d, newData, (size_t) numBytes); | |||
return true; | |||
} | |||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||
int startSample, int numSamples, int sampleDeltaToAdd) | |||
{ | |||
for (auto i = otherBuffer.findNextSamplePosition (startSample); i != otherBuffer.cend(); ++i) | |||
{ | |||
const auto metadata = *i; | |||
if (metadata.samplePosition >= startSample + numSamples && numSamples >= 0) | |||
break; | |||
addEvent (metadata.data, metadata.numBytes, metadata.samplePosition + sampleDeltaToAdd); | |||
} | |||
} | |||
int MidiBuffer::getNumEvents() const noexcept | |||
{ | |||
int n = 0; | |||
auto end = data.end(); | |||
for (auto d = data.begin(); d < end; ++n) | |||
d += MidiBufferHelpers::getEventTotalSize (d); | |||
return n; | |||
} | |||
int MidiBuffer::getFirstEventTime() const noexcept | |||
{ | |||
return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; | |||
} | |||
int MidiBuffer::getLastEventTime() const noexcept | |||
{ | |||
if (data.size() == 0) | |||
return 0; | |||
auto endData = data.end(); | |||
for (auto d = data.begin();;) | |||
{ | |||
auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||
if (nextOne >= endData) | |||
return MidiBufferHelpers::getEventTime (d); | |||
d = nextOne; | |||
} | |||
} | |||
MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const noexcept | |||
{ | |||
return std::find_if (cbegin(), cend(), [&] (const MidiMessageMetadata& metadata) noexcept | |||
{ | |||
return metadata.samplePosition >= samplePosition; | |||
}); | |||
} | |||
//============================================================================== | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | |||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept | |||
: buffer (b), iterator (b.data.begin()) | |||
{ | |||
} | |||
void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept | |||
{ | |||
iterator = buffer.findNextSamplePosition (samplePosition); | |||
} | |||
bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept | |||
{ | |||
if (iterator == buffer.cend()) | |||
return false; | |||
const auto metadata = *iterator++; | |||
midiData = metadata.data; | |||
numBytes = metadata.numBytes; | |||
samplePosition = metadata.samplePosition; | |||
return true; | |||
} | |||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||
{ | |||
if (iterator == buffer.cend()) | |||
return false; | |||
const auto metadata = *iterator++; | |||
result = metadata.getMessage(); | |||
samplePosition = metadata.samplePosition; | |||
return true; | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiBufferTest : public UnitTest | |||
{ | |||
MidiBufferTest() | |||
: UnitTest ("MidiBuffer", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("Clear messages"); | |||
{ | |||
const auto message = MidiMessage::noteOn (1, 64, 0.5f); | |||
const auto testBuffer = [&] | |||
{ | |||
MidiBuffer buffer; | |||
buffer.addEvent (message, 0); | |||
buffer.addEvent (message, 10); | |||
buffer.addEvent (message, 20); | |||
buffer.addEvent (message, 30); | |||
return buffer; | |||
}(); | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 0); | |||
expectEquals (buffer.getNumEvents(), 4); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 1); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 10); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 20); | |||
expectEquals (buffer.getNumEvents(), 2); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 30); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 300); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
} | |||
} | |||
}; | |||
static MidiBufferTest midiBufferTest; | |||
#endif | |||
} // namespace juce |
@@ -1,345 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
A view of MIDI message data stored in a contiguous buffer. | |||
Instances of this class do *not* own the midi data bytes that they point to. | |||
Instead, they expect the midi data to live in a separate buffer that outlives | |||
the MidiMessageMetadata instance. | |||
@tags{Audio} | |||
*/ | |||
struct MidiMessageMetadata final | |||
{ | |||
MidiMessageMetadata() noexcept = default; | |||
MidiMessageMetadata (const uint8* dataIn, int numBytesIn, int positionIn) noexcept | |||
: data (dataIn), numBytes (numBytesIn), samplePosition (positionIn) | |||
{ | |||
} | |||
/** Constructs a new MidiMessage instance from the data that this object is viewing. | |||
Note that MidiMessage owns its data storage, whereas MidiMessageMetadata does not. | |||
*/ | |||
MidiMessage getMessage() const { return MidiMessage (data, numBytes, samplePosition); } | |||
/** Pointer to the first byte of a MIDI message. */ | |||
const uint8* data = nullptr; | |||
/** The number of bytes in the MIDI message. */ | |||
int numBytes = 0; | |||
/** The MIDI message's timestamp. */ | |||
int samplePosition = 0; | |||
}; | |||
//============================================================================== | |||
/** | |||
An iterator to move over contiguous raw MIDI data, which Allows iterating | |||
over a MidiBuffer using C++11 range-for syntax. | |||
In the following example, we log all three-byte messages in a midi buffer. | |||
@code | |||
void processBlock (AudioBuffer<float>&, MidiBuffer& midiBuffer) override | |||
{ | |||
for (const MidiMessageMetadata metadata : midiBuffer) | |||
if (metadata.numBytes == 3) | |||
Logger::writeToLog (metadata.getMessage().getDescription()); | |||
} | |||
@endcode | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiBufferIterator | |||
{ | |||
using Ptr = const uint8*; | |||
public: | |||
MidiBufferIterator() = default; | |||
/** Constructs an iterator pointing at the message starting at the byte `dataIn`. | |||
`dataIn` must point to the start of a valid MIDI message. If it does not, | |||
calling other member functions on the iterator will result in undefined | |||
behaviour. | |||
*/ | |||
explicit MidiBufferIterator (const uint8* dataIn) noexcept | |||
: data (dataIn) | |||
{ | |||
} | |||
using difference_type = std::iterator_traits<Ptr>::difference_type; | |||
using value_type = MidiMessageMetadata; | |||
using reference = MidiMessageMetadata; | |||
using pointer = void; | |||
using iterator_category = std::input_iterator_tag; | |||
/** Make this iterator point to the next message in the buffer. */ | |||
MidiBufferIterator& operator++() noexcept; | |||
/** Create a copy of this object, make this iterator point to the next message in | |||
the buffer, then return the copy. | |||
*/ | |||
MidiBufferIterator operator++ (int) noexcept; | |||
/** Return true if this iterator points to the same message as another | |||
iterator instance, otherwise return false. | |||
*/ | |||
bool operator== (const MidiBufferIterator& other) const noexcept { return data == other.data; } | |||
/** Return false if this iterator points to the same message as another | |||
iterator instance, otherwise returns true. | |||
*/ | |||
bool operator!= (const MidiBufferIterator& other) const noexcept { return ! operator== (other); } | |||
/** Return an instance of MidiMessageMetadata which describes the message to which | |||
the iterator is currently pointing. | |||
*/ | |||
reference operator*() const noexcept; | |||
private: | |||
Ptr data = nullptr; | |||
}; | |||
//============================================================================== | |||
/** | |||
Holds a sequence of time-stamped midi events. | |||
Analogous to the AudioBuffer, this holds a set of midi events with | |||
integer time-stamps. The buffer is kept sorted in order of the time-stamps. | |||
If you're working with a sequence of midi events that may need to be manipulated | |||
or read/written to a midi file, then MidiMessageSequence is probably a more | |||
appropriate container. MidiBuffer is designed for lower-level streams of raw | |||
midi data. | |||
@see MidiMessage | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiBuffer | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty MidiBuffer. */ | |||
MidiBuffer() noexcept = default; | |||
/** Creates a MidiBuffer containing a single midi message. */ | |||
explicit MidiBuffer (const MidiMessage& message) noexcept; | |||
//============================================================================== | |||
/** Removes all events from the buffer. */ | |||
void clear() noexcept; | |||
/** Removes all events between two times from the buffer. | |||
All events for which (start <= event position < start + numSamples) will | |||
be removed. | |||
*/ | |||
void clear (int start, int numSamples); | |||
/** Returns true if the buffer is empty. | |||
To actually retrieve the events, use a MidiBufferIterator object | |||
*/ | |||
bool isEmpty() const noexcept; | |||
/** Counts the number of events in the buffer. | |||
This is actually quite a slow operation, as it has to iterate through all | |||
the events, so you might prefer to call isEmpty() if that's all you need | |||
to know. | |||
*/ | |||
int getNumEvents() const noexcept; | |||
/** Adds an event to the buffer. | |||
The sample number will be used to determine the position of the event in | |||
the buffer, which is always kept sorted. The MidiMessage's timestamp is | |||
ignored. | |||
If an event is added whose sample position is the same as one or more events | |||
already in the buffer, the new event will be placed after the existing ones. | |||
To retrieve events, use a MidiBufferIterator object. | |||
Returns true on success, or false on failure. | |||
*/ | |||
bool addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||
/** Adds an event to the buffer from raw midi data. | |||
The sample number will be used to determine the position of the event in | |||
the buffer, which is always kept sorted. | |||
If an event is added whose sample position is the same as one or more events | |||
already in the buffer, the new event will be placed after the existing ones. | |||
The event data will be inspected to calculate the number of bytes in length that | |||
the midi event really takes up, so maxBytesOfMidiData may be longer than the data | |||
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, | |||
it'll actually only store 3 bytes. If the midi data is invalid, it might not | |||
add an event at all. | |||
To retrieve events, use a MidiBufferIterator object. | |||
Returns true on success, or false on failure. | |||
*/ | |||
bool addEvent (const void* rawMidiData, | |||
int maxBytesOfMidiData, | |||
int sampleNumber); | |||
/** Adds some events from another buffer to this one. | |||
@param otherBuffer the buffer containing the events you want to add | |||
@param startSample the lowest sample number in the source buffer for which | |||
events should be added. Any source events whose timestamp is | |||
less than this will be ignored | |||
@param numSamples the valid range of samples from the source buffer for which | |||
events should be added - i.e. events in the source buffer whose | |||
timestamp is greater than or equal to (startSample + numSamples) | |||
will be ignored. If this value is less than 0, all events after | |||
startSample will be taken. | |||
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events | |||
that are added to this buffer | |||
*/ | |||
void addEvents (const MidiBuffer& otherBuffer, | |||
int startSample, | |||
int numSamples, | |||
int sampleDeltaToAdd); | |||
/** Returns the sample number of the first event in the buffer. | |||
If the buffer's empty, this will just return 0. | |||
*/ | |||
int getFirstEventTime() const noexcept; | |||
/** Returns the sample number of the last event in the buffer. | |||
If the buffer's empty, this will just return 0. | |||
*/ | |||
int getLastEventTime() const noexcept; | |||
//============================================================================== | |||
/** Exchanges the contents of this buffer with another one. | |||
This is a quick operation, because no memory allocating or copying is done, it | |||
just swaps the internal state of the two buffers. | |||
*/ | |||
void swapWith (MidiBuffer&) noexcept; | |||
/** Preallocates some memory for the buffer to use. | |||
This helps to avoid needing to reallocate space when the buffer has messages | |||
added to it. | |||
*/ | |||
void ensureSize (size_t minimumNumBytes); | |||
/** Get a read-only iterator pointing to the beginning of this buffer. */ | |||
MidiBufferIterator begin() const noexcept { return cbegin(); } | |||
/** Get a read-only iterator pointing one past the end of this buffer. */ | |||
MidiBufferIterator end() const noexcept { return cend(); } | |||
/** Get a read-only iterator pointing to the beginning of this buffer. */ | |||
MidiBufferIterator cbegin() const noexcept { return MidiBufferIterator (data.begin()); } | |||
/** Get a read-only iterator pointing one past the end of this buffer. */ | |||
MidiBufferIterator cend() const noexcept { return MidiBufferIterator (data.end()); } | |||
/** Get an iterator pointing to the first event with a timestamp greater-than or | |||
equal-to `samplePosition`. | |||
*/ | |||
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept; | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
/** This class is now deprecated in favour of MidiBufferIterator. | |||
Used to iterate through the events in a MidiBuffer. | |||
Note that altering the buffer while an iterator is using it will produce | |||
undefined behaviour. | |||
@see MidiBuffer | |||
*/ | |||
class [[deprecated]] JUCE_API Iterator | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an Iterator for this MidiBuffer. */ | |||
Iterator (const MidiBuffer& b) noexcept; | |||
//============================================================================== | |||
/** Repositions the iterator so that the next event retrieved will be the first | |||
one whose sample position is at greater than or equal to the given position. | |||
*/ | |||
void setNextSamplePosition (int samplePosition) noexcept; | |||
/** Retrieves a copy of the next event from the buffer. | |||
@param result on return, this will be the message. The MidiMessage's timestamp | |||
is set to the same value as samplePosition. | |||
@param samplePosition on return, this will be the position of the event, as a | |||
sample index in the buffer | |||
@returns true if an event was found, or false if the iterator has reached | |||
the end of the buffer | |||
*/ | |||
bool getNextEvent (MidiMessage& result, | |||
int& samplePosition) noexcept; | |||
/** Retrieves the next event from the buffer. | |||
@param midiData on return, this pointer will be set to a block of data containing | |||
the midi message. Note that to make it fast, this is a pointer | |||
directly into the MidiBuffer's internal data, so is only valid | |||
temporarily until the MidiBuffer is altered. | |||
@param numBytesOfMidiData on return, this is the number of bytes of data used by the | |||
midi message | |||
@param samplePosition on return, this will be the position of the event, as a | |||
sample index in the buffer | |||
@returns true if an event was found, or false if the iterator has reached | |||
the end of the buffer | |||
*/ | |||
bool getNextEvent (const uint8* &midiData, | |||
int& numBytesOfMidiData, | |||
int& samplePosition) noexcept; | |||
private: | |||
//============================================================================== | |||
const MidiBuffer& buffer; | |||
MidiBufferIterator iterator; | |||
}; | |||
#endif | |||
/** The raw data holding this buffer. | |||
Obviously access to this data is provided at your own risk. Its internal format could | |||
change in future, so don't write code that relies on it! | |||
*/ | |||
Array<uint8> data; | |||
private: | |||
JUCE_LEAK_DETECTOR (MidiBuffer) | |||
}; | |||
} // namespace juce |
@@ -1,188 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Helper class that takes chunks of incoming midi bytes, packages them into | |||
messages, and dispatches them to a midi callback. | |||
@tags{Audio} | |||
*/ | |||
class MidiDataConcatenator | |||
{ | |||
public: | |||
MidiDataConcatenator (int initialBufferSize) | |||
: pendingSysexData ((size_t) initialBufferSize) | |||
{ | |||
} | |||
void reset() | |||
{ | |||
currentMessageLen = 0; | |||
pendingSysexSize = 0; | |||
pendingSysexTime = 0; | |||
} | |||
template <typename UserDataType, typename CallbackType> | |||
void pushMidiData (const void* inputData, int numBytes, double time, | |||
UserDataType* input, CallbackType& callback) | |||
{ | |||
auto d = static_cast<const uint8*> (inputData); | |||
while (numBytes > 0) | |||
{ | |||
auto nextByte = *d; | |||
if (pendingSysexSize != 0 || nextByte == 0xf0) | |||
{ | |||
processSysex (d, numBytes, time, input, callback); | |||
currentMessageLen = 0; | |||
continue; | |||
} | |||
++d; | |||
--numBytes; | |||
if (isRealtimeMessage (nextByte)) | |||
{ | |||
callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time)); | |||
// These can be embedded in the middle of a normal message, so we won't | |||
// reset the currentMessageLen here. | |||
continue; | |||
} | |||
if (isInitialByte (nextByte)) | |||
{ | |||
currentMessage[0] = nextByte; | |||
currentMessageLen = 1; | |||
} | |||
else if (currentMessageLen > 0 && currentMessageLen < 3) | |||
{ | |||
currentMessage[currentMessageLen++] = nextByte; | |||
} | |||
else | |||
{ | |||
// message is too long or invalid MIDI - abandon it and start again with the next byte | |||
currentMessageLen = 0; | |||
continue; | |||
} | |||
auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]); | |||
if (expectedLength == currentMessageLen) | |||
{ | |||
callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time)); | |||
currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte | |||
} | |||
} | |||
} | |||
private: | |||
template <typename UserDataType, typename CallbackType> | |||
void processSysex (const uint8*& d, int& numBytes, double time, | |||
UserDataType* input, CallbackType& callback) | |||
{ | |||
if (*d == 0xf0) | |||
{ | |||
pendingSysexSize = 0; | |||
pendingSysexTime = time; | |||
} | |||
pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false); | |||
auto totalMessage = static_cast<uint8*> (pendingSysexData.getData()); | |||
auto dest = totalMessage + pendingSysexSize; | |||
do | |||
{ | |||
if (pendingSysexSize > 0 && isStatusByte (*d)) | |||
{ | |||
if (*d == 0xf7) | |||
{ | |||
*dest++ = *d++; | |||
++pendingSysexSize; | |||
--numBytes; | |||
break; | |||
} | |||
if (*d >= 0xfa || *d == 0xf8) | |||
{ | |||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); | |||
++d; | |||
--numBytes; | |||
} | |||
else | |||
{ | |||
pendingSysexSize = 0; | |||
int used = 0; | |||
const MidiMessage m (d, numBytes, used, 0, time); | |||
if (used > 0) | |||
{ | |||
callback.handleIncomingMidiMessage (input, m); | |||
numBytes -= used; | |||
d += used; | |||
} | |||
break; | |||
} | |||
} | |||
else | |||
{ | |||
*dest++ = *d++; | |||
++pendingSysexSize; | |||
--numBytes; | |||
} | |||
} | |||
while (numBytes > 0); | |||
if (pendingSysexSize > 0) | |||
{ | |||
if (totalMessage [pendingSysexSize - 1] == 0xf7) | |||
{ | |||
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime)); | |||
pendingSysexSize = 0; | |||
} | |||
else | |||
{ | |||
callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime); | |||
} | |||
} | |||
} | |||
static bool isRealtimeMessage (uint8 byte) { return byte >= 0xf8 && byte <= 0xfe; } | |||
static bool isStatusByte (uint8 byte) { return byte >= 0x80; } | |||
static bool isInitialByte (uint8 byte) { return isStatusByte (byte) && byte != 0xf7; } | |||
uint8 currentMessage[3]; | |||
int currentMessageLen = 0; | |||
MemoryBlock pendingSysexData; | |||
double pendingSysexTime = 0; | |||
int pendingSysexSize = 0; | |||
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator) | |||
}; | |||
} // namespace juce |
@@ -1,794 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace MidiFileHelpers | |||
{ | |||
static void writeVariableLengthInt (OutputStream& out, uint32 v) | |||
{ | |||
auto buffer = v & 0x7f; | |||
while ((v >>= 7) != 0) | |||
{ | |||
buffer <<= 8; | |||
buffer |= ((v & 0x7f) | 0x80); | |||
} | |||
for (;;) | |||
{ | |||
out.writeByte ((char) buffer); | |||
if (buffer & 0x80) | |||
buffer >>= 8; | |||
else | |||
break; | |||
} | |||
} | |||
template <typename Integral> | |||
struct ReadTrait; | |||
template <> | |||
struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; }; | |||
template <> | |||
struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; }; | |||
template <typename Integral> | |||
Optional<Integral> tryRead (const uint8*& data, size_t& remaining) | |||
{ | |||
using Trait = ReadTrait<Integral>; | |||
constexpr auto size = sizeof (Integral); | |||
if (remaining < size) | |||
return {}; | |||
const Optional<Integral> result { Trait::read (data) }; | |||
data += size; | |||
remaining -= size; | |||
return result; | |||
} | |||
struct HeaderDetails | |||
{ | |||
size_t bytesRead = 0; | |||
short timeFormat = 0; | |||
short fileType = 0; | |||
short numberOfTracks = 0; | |||
}; | |||
static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData, | |||
const size_t maxSize) | |||
{ | |||
auto* data = initialData; | |||
auto remaining = maxSize; | |||
auto ch = tryRead<uint32> (data, remaining); | |||
if (! ch.hasValue()) | |||
return {}; | |||
if (*ch != ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
auto ok = false; | |||
if (*ch == ByteOrder::bigEndianInt ("RIFF")) | |||
{ | |||
for (int i = 0; i < 8; ++i) | |||
{ | |||
ch = tryRead<uint32> (data, remaining); | |||
if (! ch.hasValue()) | |||
return {}; | |||
if (*ch == ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
ok = true; | |||
break; | |||
} | |||
} | |||
} | |||
if (! ok) | |||
return {}; | |||
} | |||
const auto bytesRemaining = tryRead<uint32> (data, remaining); | |||
if (! bytesRemaining.hasValue() || *bytesRemaining > remaining) | |||
return {}; | |||
const auto optFileType = tryRead<uint16> (data, remaining); | |||
if (! optFileType.hasValue() || 2 < *optFileType) | |||
return {}; | |||
const auto optNumTracks = tryRead<uint16> (data, remaining); | |||
if (! optNumTracks.hasValue() || (*optFileType == 0 && *optNumTracks != 1)) | |||
return {}; | |||
const auto optTimeFormat = tryRead<uint16> (data, remaining); | |||
if (! optTimeFormat.hasValue()) | |||
return {}; | |||
HeaderDetails result; | |||
result.fileType = (short) *optFileType; | |||
result.timeFormat = (short) *optTimeFormat; | |||
result.numberOfTracks = (short) *optNumTracks; | |||
result.bytesRead = maxSize - remaining; | |||
return { result }; | |||
} | |||
static double convertTicksToSeconds (double time, | |||
const MidiMessageSequence& tempoEvents, | |||
int timeFormat) | |||
{ | |||
if (timeFormat < 0) | |||
return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); | |||
double lastTime = 0, correctedTime = 0; | |||
auto tickLen = 1.0 / (timeFormat & 0x7fff); | |||
auto secsPerTick = 0.5 * tickLen; | |||
auto numEvents = tempoEvents.getNumEvents(); | |||
for (int i = 0; i < numEvents; ++i) | |||
{ | |||
auto& m = tempoEvents.getEventPointer(i)->message; | |||
auto eventTime = m.getTimeStamp(); | |||
if (eventTime >= time) | |||
break; | |||
correctedTime += (eventTime - lastTime) * secsPerTick; | |||
lastTime = eventTime; | |||
if (m.isTempoMetaEvent()) | |||
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); | |||
while (i + 1 < numEvents) | |||
{ | |||
auto& m2 = tempoEvents.getEventPointer(i + 1)->message; | |||
if (m2.getTimeStamp() != eventTime) | |||
break; | |||
if (m2.isTempoMetaEvent()) | |||
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); | |||
++i; | |||
} | |||
} | |||
return correctedTime + (time - lastTime) * secsPerTick; | |||
} | |||
template <typename MethodType> | |||
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks, | |||
MidiMessageSequence& results, | |||
MethodType method) | |||
{ | |||
for (auto* track : tracks) | |||
{ | |||
auto numEvents = track->getNumEvents(); | |||
for (int j = 0; j < numEvents; ++j) | |||
{ | |||
auto& m = track->getEventPointer(j)->message; | |||
if ((m.*method)()) | |||
results.addEvent (m); | |||
} | |||
} | |||
} | |||
static MidiMessageSequence readTrack (const uint8* data, int size) | |||
{ | |||
double time = 0; | |||
uint8 lastStatusByte = 0; | |||
MidiMessageSequence result; | |||
while (size > 0) | |||
{ | |||
const auto delay = MidiMessage::readVariableLengthValue (data, (int) size); | |||
if (! delay.isValid()) | |||
break; | |||
data += delay.bytesUsed; | |||
size -= delay.bytesUsed; | |||
time += delay.value; | |||
if (size <= 0) | |||
break; | |||
int messSize = 0; | |||
const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||
if (messSize <= 0) | |||
break; | |||
size -= messSize; | |||
data += messSize; | |||
result.addEvent (mm); | |||
auto firstByte = *(mm.getRawData()); | |||
if ((firstByte & 0xf0) != 0xf0) | |||
lastStatusByte = firstByte; | |||
} | |||
return result; | |||
} | |||
} | |||
//============================================================================== | |||
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {} | |||
MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat) | |||
{ | |||
tracks.addCopiesOf (other.tracks); | |||
} | |||
MidiFile& MidiFile::operator= (const MidiFile& other) | |||
{ | |||
tracks.clear(); | |||
tracks.addCopiesOf (other.tracks); | |||
timeFormat = other.timeFormat; | |||
return *this; | |||
} | |||
MidiFile::MidiFile (MidiFile&& other) | |||
: tracks (std::move (other.tracks)), | |||
timeFormat (other.timeFormat) | |||
{ | |||
} | |||
MidiFile& MidiFile::operator= (MidiFile&& other) | |||
{ | |||
tracks = std::move (other.tracks); | |||
timeFormat = other.timeFormat; | |||
return *this; | |||
} | |||
void MidiFile::clear() | |||
{ | |||
tracks.clear(); | |||
} | |||
//============================================================================== | |||
int MidiFile::getNumTracks() const noexcept | |||
{ | |||
return tracks.size(); | |||
} | |||
const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept | |||
{ | |||
return tracks[index]; | |||
} | |||
void MidiFile::addTrack (const MidiMessageSequence& trackSequence) | |||
{ | |||
tracks.add (new MidiMessageSequence (trackSequence)); | |||
} | |||
//============================================================================== | |||
short MidiFile::getTimeFormat() const noexcept | |||
{ | |||
return timeFormat; | |||
} | |||
void MidiFile::setTicksPerQuarterNote (int ticks) noexcept | |||
{ | |||
timeFormat = (short) ticks; | |||
} | |||
void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept | |||
{ | |||
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); | |||
} | |||
//============================================================================== | |||
void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const | |||
{ | |||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent); | |||
} | |||
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const | |||
{ | |||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent); | |||
} | |||
void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const | |||
{ | |||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent); | |||
} | |||
double MidiFile::getLastTimestamp() const | |||
{ | |||
double t = 0.0; | |||
for (auto* ms : tracks) | |||
t = jmax (t, ms->getEndTime()); | |||
return t; | |||
} | |||
//============================================================================== | |||
bool MidiFile::readFrom (InputStream& sourceStream, | |||
bool createMatchingNoteOffs, | |||
int* fileType) | |||
{ | |||
clear(); | |||
MemoryBlock data; | |||
const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||
// (put a sanity-check on the file size, as midi files are generally small) | |||
if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
return false; | |||
auto size = data.getSize(); | |||
auto d = static_cast<const uint8*> (data.getData()); | |||
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); | |||
if (! optHeader.hasValue()) | |||
return false; | |||
const auto header = *optHeader; | |||
timeFormat = header.timeFormat; | |||
d += header.bytesRead; | |||
size -= (size_t) header.bytesRead; | |||
for (int track = 0; track < header.numberOfTracks; ++track) | |||
{ | |||
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkType.hasValue()) | |||
return false; | |||
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkSize.hasValue()) | |||
return false; | |||
const auto chunkSize = *optChunkSize; | |||
if (size < chunkSize) | |||
return false; | |||
if (*optChunkType == ByteOrder::bigEndianInt ("MTrk")) | |||
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); | |||
size -= chunkSize; | |||
d += chunkSize; | |||
} | |||
const auto successful = (size == 0); | |||
if (successful && fileType != nullptr) | |||
*fileType = header.fileType; | |||
return successful; | |||
} | |||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||
{ | |||
auto sequence = MidiFileHelpers::readTrack (data, size); | |||
// sort so that we put all the note-offs before note-ons that have the same time | |||
std::stable_sort (sequence.list.begin(), sequence.list.end(), | |||
[] (const MidiMessageSequence::MidiEventHolder* a, | |||
const MidiMessageSequence::MidiEventHolder* b) | |||
{ | |||
auto t1 = a->message.getTimeStamp(); | |||
auto t2 = b->message.getTimeStamp(); | |||
if (t1 < t2) return true; | |||
if (t2 < t1) return false; | |||
return a->message.isNoteOff() && b->message.isNoteOn(); | |||
}); | |||
if (createMatchingNoteOffs) | |||
sequence.updateMatchedPairs(); | |||
addTrack (sequence); | |||
} | |||
//============================================================================== | |||
void MidiFile::convertTimestampTicksToSeconds() | |||
{ | |||
MidiMessageSequence tempoEvents; | |||
findAllTempoEvents (tempoEvents); | |||
findAllTimeSigEvents (tempoEvents); | |||
if (timeFormat != 0) | |||
{ | |||
for (auto* ms : tracks) | |||
{ | |||
for (int j = ms->getNumEvents(); --j >= 0;) | |||
{ | |||
auto& m = ms->getEventPointer(j)->message; | |||
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); | |||
} | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
bool MidiFile::writeTo (OutputStream& out, int midiFileType) const | |||
{ | |||
jassert (midiFileType >= 0 && midiFileType <= 2); | |||
if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false; | |||
if (! out.writeIntBigEndian (6)) return false; | |||
if (! out.writeShortBigEndian ((short) midiFileType)) return false; | |||
if (! out.writeShortBigEndian ((short) tracks.size())) return false; | |||
if (! out.writeShortBigEndian (timeFormat)) return false; | |||
for (auto* ms : tracks) | |||
if (! writeTrack (out, *ms)) | |||
return false; | |||
out.flush(); | |||
return true; | |||
} | |||
bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const | |||
{ | |||
MemoryOutputStream out; | |||
int lastTick = 0; | |||
uint8 lastStatusByte = 0; | |||
bool endOfTrackEventWritten = false; | |||
for (int i = 0; i < ms.getNumEvents(); ++i) | |||
{ | |||
auto& mm = ms.getEventPointer(i)->message; | |||
if (mm.isEndOfTrackMetaEvent()) | |||
endOfTrackEventWritten = true; | |||
auto tick = roundToInt (mm.getTimeStamp()); | |||
auto delta = jmax (0, tick - lastTick); | |||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); | |||
lastTick = tick; | |||
auto* data = mm.getRawData(); | |||
auto dataSize = mm.getRawDataSize(); | |||
auto statusByte = data[0]; | |||
if (statusByte == lastStatusByte | |||
&& (statusByte & 0xf0) != 0xf0 | |||
&& dataSize > 1 | |||
&& i > 0) | |||
{ | |||
++data; | |||
--dataSize; | |||
} | |||
else if (statusByte == 0xf0) // Write sysex message with length bytes. | |||
{ | |||
out.writeByte ((char) statusByte); | |||
++data; | |||
--dataSize; | |||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); | |||
} | |||
out.write (data, (size_t) dataSize); | |||
lastStatusByte = statusByte; | |||
} | |||
if (! endOfTrackEventWritten) | |||
{ | |||
out.writeByte (0); // (tick delta) | |||
auto m = MidiMessage::endOfTrack(); | |||
out.write (m.getRawData(), (size_t) m.getRawDataSize()); | |||
} | |||
if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false; | |||
if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false; | |||
mainOut << out; | |||
return true; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiFileTest : public UnitTest | |||
{ | |||
MidiFileTest() | |||
: UnitTest ("MidiFile", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("ReadTrack respects running status"); | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
MidiFileHelpers::writeVariableLengthInt (os, 100); | |||
writeBytes (os, { 0x90, 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 200); | |||
writeBytes (os, { 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 300); | |||
writeBytes (os, { 0xff, 0x2f, 0x00 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 3); | |||
expect (sequence.getEventPointer (0)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (1)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent()); | |||
} | |||
beginTest ("ReadTrack returns available messages if input is truncated"); | |||
{ | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Incomplete delta time | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with no following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with malformed following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
writeBytes (os, { 0x90, 0x40 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 1); | |||
expect (sequence.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40); | |||
expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00); | |||
} | |||
} | |||
beginTest ("Header parsing works"); | |||
{ | |||
{ | |||
// No data | |||
const auto header = parseHeader ([] (OutputStream&) {}); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
// Invalid initial byte | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
// Type block, but no header data | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
// We (ll-formed header, but track type is 0 and channels != 1 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
// Well-formed header, but track type is 5 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
// Well-formed header | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); | |||
}); | |||
expect (header.hasValue()); | |||
expectEquals (header->fileType, (short) 1); | |||
expectEquals (header->numberOfTracks, (short) 16); | |||
expectEquals (header->timeFormat, (short) 1); | |||
expectEquals ((int) header->bytesRead, 14); | |||
} | |||
} | |||
beginTest ("Read from stream"); | |||
{ | |||
{ | |||
// Empty input | |||
const auto file = parseFile ([] (OutputStream&) {}); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
// Malformed header | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
// Header, no channels | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); | |||
}); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 0); | |||
} | |||
{ | |||
// Header, one malformed channel | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', '?' }); | |||
}); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
// Header, one channel with malformed message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); | |||
}); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 1); | |||
expectEquals (file->getTrack (0)->getNumEvents(), 0); | |||
} | |||
{ | |||
// Header, one channel with incorrect length message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); | |||
}); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
// Header, one channel, all well-formed | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 0x0f); | |||
writeBytes (os, { 0x80, 0x00, 0x00 }); | |||
}); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 1); | |||
auto& track = *file->getTrack (0); | |||
expectEquals (track.getNumEvents(), 1); | |||
expect (track.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); | |||
} | |||
} | |||
} | |||
template <typename Fn> | |||
static MidiMessageSequence parseSequence (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()), | |||
(int) os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()), | |||
os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static Optional<MidiFile> parseFile (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
MemoryInputStream is (os.getData(), os.getDataSize(), false); | |||
MidiFile mf; | |||
int fileType = 0; | |||
if (mf.readFrom (is, true, &fileType)) | |||
return mf; | |||
return {}; | |||
} | |||
static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes) | |||
{ | |||
for (const auto& byte : bytes) | |||
os.writeByte ((char) byte); | |||
} | |||
}; | |||
static MidiFileTest midiFileTests; | |||
#endif | |||
} // namespace juce |
@@ -1,198 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Reads/writes standard midi format files. | |||
To read a midi file, create a MidiFile object and call its readFrom() method. You | |||
can then get the individual midi tracks from it using the getTrack() method. | |||
To write a file, create a MidiFile object, add some MidiMessageSequence objects | |||
to it using the addTrack() method, and then call its writeTo() method to stream | |||
it out. | |||
@see MidiMessageSequence | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiFile | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty MidiFile object. */ | |||
MidiFile(); | |||
/** Creates a copy of another MidiFile. */ | |||
MidiFile (const MidiFile&); | |||
/** Copies from another MidiFile object */ | |||
MidiFile& operator= (const MidiFile&); | |||
/** Creates a copy of another MidiFile. */ | |||
MidiFile (MidiFile&&); | |||
/** Copies from another MidiFile object */ | |||
MidiFile& operator= (MidiFile&&); | |||
//============================================================================== | |||
/** Returns the number of tracks in the file. | |||
@see getTrack, addTrack | |||
*/ | |||
int getNumTracks() const noexcept; | |||
/** Returns a pointer to one of the tracks in the file. | |||
@returns a pointer to the track, or nullptr if the index is out-of-range | |||
@see getNumTracks, addTrack | |||
*/ | |||
const MidiMessageSequence* getTrack (int index) const noexcept; | |||
/** Adds a midi track to the file. | |||
This will make its own internal copy of the sequence that is passed-in. | |||
@see getNumTracks, getTrack | |||
*/ | |||
void addTrack (const MidiMessageSequence& trackSequence); | |||
/** Removes all midi tracks from the file. | |||
@see getNumTracks | |||
*/ | |||
void clear(); | |||
/** Returns the raw time format code that will be written to a stream. | |||
After reading a midi file, this method will return the time-format that | |||
was read from the file's header. It can be changed using the setTicksPerQuarterNote() | |||
or setSmpteTimeFormat() methods. | |||
If the value returned is positive, it indicates the number of midi ticks | |||
per quarter-note - see setTicksPerQuarterNote(). | |||
It it's negative, the upper byte indicates the frames-per-second (but negative), and | |||
the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). | |||
*/ | |||
short getTimeFormat() const noexcept; | |||
/** Sets the time format to use when this file is written to a stream. | |||
If this is called, the file will be written as bars/beats using the | |||
specified resolution, rather than SMPTE absolute times, as would be | |||
used if setSmpteTimeFormat() had been called instead. | |||
@param ticksPerQuarterNote e.g. 96, 960 | |||
@see setSmpteTimeFormat | |||
*/ | |||
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; | |||
/** Sets the time format to use when this file is written to a stream. | |||
If this is called, the file will be written using absolute times, rather | |||
than bars/beats as would be the case if setTicksPerBeat() had been called | |||
instead. | |||
@param framesPerSecond must be 24, 25, 29 or 30 | |||
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code), | |||
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond | |||
timing, setSmpteTimeFormat (25, 40) | |||
@see setTicksPerBeat | |||
*/ | |||
void setSmpteTimeFormat (int framesPerSecond, | |||
int subframeResolution) noexcept; | |||
//============================================================================== | |||
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file. | |||
Useful for finding the positions of all the tempo changes in a file. | |||
@param tempoChangeEvents a list to which all the events will be added | |||
*/ | |||
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; | |||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||
Useful for finding the positions of all the tempo changes in a file. | |||
@param timeSigEvents a list to which all the events will be added | |||
*/ | |||
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; | |||
/** Makes a list of all the key-signature meta-events from all tracks in the midi file. | |||
@param keySigEvents a list to which all the events will be added | |||
*/ | |||
void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const; | |||
/** Returns the latest timestamp in any of the tracks. | |||
(Useful for finding the length of the file). | |||
*/ | |||
double getLastTimestamp() const; | |||
//============================================================================== | |||
/** Reads a midi file format stream. | |||
After calling this, you can get the tracks that were read from the file by using the | |||
getNumTracks() and getTrack() methods. | |||
The timestamps of the midi events in the tracks will represent their positions in | |||
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() | |||
method. | |||
@param sourceStream the source stream | |||
@param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will | |||
be automatically added at the end of the file by calling | |||
MidiMessageSequence::updateMatchedPairs on each track. | |||
@param midiFileType if not nullptr, the integer at this address will be set | |||
to 0, 1, or 2 depending on the type of the midi file | |||
@returns true if the stream was read successfully | |||
*/ | |||
bool readFrom (InputStream& sourceStream, | |||
bool createMatchingNoteOffs = true, | |||
int* midiFileType = nullptr); | |||
/** Writes the midi tracks as a standard midi file. | |||
The midiFileType value is written as the file's format type, which can be 0, 1 | |||
or 2 - see the midi file spec for more info about that. | |||
@param destStream the destination stream | |||
@param midiFileType the type of midi file | |||
@returns true if the operation succeeded. | |||
*/ | |||
bool writeTo (OutputStream& destStream, int midiFileType = 1) const; | |||
/** Converts the timestamp of all the midi events from midi ticks to seconds. | |||
This will use the midi time format and tempo/time signature info in the | |||
tracks to convert all the timestamps to absolute values in seconds. | |||
*/ | |||
void convertTimestampTicksToSeconds(); | |||
private: | |||
//============================================================================== | |||
OwnedArray<MidiMessageSequence> tracks; | |||
short timeFormat; | |||
void readNextTrack (const uint8*, int, bool); | |||
bool writeTrack (OutputStream&, const MidiMessageSequence&) const; | |||
JUCE_LEAK_DETECTOR (MidiFile) | |||
}; | |||
} // namespace juce |
@@ -1,173 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MidiKeyboardState::MidiKeyboardState() | |||
{ | |||
zerostruct (noteStates); | |||
} | |||
//============================================================================== | |||
void MidiKeyboardState::reset() | |||
{ | |||
const ScopedLock sl (lock); | |||
zerostruct (noteStates); | |||
eventsToAdd.clear(); | |||
} | |||
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
return isPositiveAndBelow (n, 128) | |||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0; | |||
} | |||
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept | |||
{ | |||
return isPositiveAndBelow (n, 128) | |||
&& (noteStates[n] & midiChannelMask) != 0; | |||
} | |||
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (isPositiveAndBelow (midiNoteNumber, 128)); | |||
const ScopedLock sl (lock); | |||
if (isPositiveAndBelow (midiNoteNumber, 128)) | |||
{ | |||
const int timeNow = (int) Time::getMillisecondCounter(); | |||
eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); | |||
eventsToAdd.clear (0, timeNow - 500); | |||
noteOnInternal (midiChannel, midiNoteNumber, velocity); | |||
} | |||
} | |||
void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) | |||
{ | |||
if (isPositiveAndBelow (midiNoteNumber, 128)) | |||
{ | |||
noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] | (1 << (midiChannel - 1))); | |||
listeners.call ([&] (Listener& l) { l.handleNoteOn (this, midiChannel, midiNoteNumber, velocity); }); | |||
} | |||
} | |||
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (isNoteOn (midiChannel, midiNoteNumber)) | |||
{ | |||
const int timeNow = (int) Time::getMillisecondCounter(); | |||
eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); | |||
eventsToAdd.clear (0, timeNow - 500); | |||
noteOffInternal (midiChannel, midiNoteNumber, velocity); | |||
} | |||
} | |||
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity) | |||
{ | |||
if (isNoteOn (midiChannel, midiNoteNumber)) | |||
{ | |||
noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] & ~(1 << (midiChannel - 1))); | |||
listeners.call ([&] (Listener& l) { l.handleNoteOff (this, midiChannel, midiNoteNumber, velocity); }); | |||
} | |||
} | |||
void MidiKeyboardState::allNotesOff (const int midiChannel) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (midiChannel <= 0) | |||
{ | |||
for (int i = 1; i <= 16; ++i) | |||
allNotesOff (i); | |||
} | |||
else | |||
{ | |||
for (int i = 0; i < 128; ++i) | |||
noteOff (midiChannel, i, 0.0f); | |||
} | |||
} | |||
void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) | |||
{ | |||
if (message.isNoteOn()) | |||
{ | |||
noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); | |||
} | |||
else if (message.isNoteOff()) | |||
{ | |||
noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); | |||
} | |||
else if (message.isAllNotesOff()) | |||
{ | |||
for (int i = 0; i < 128; ++i) | |||
noteOffInternal (message.getChannel(), i, 0.0f); | |||
} | |||
} | |||
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, | |||
const int startSample, | |||
const int numSamples, | |||
const bool injectIndirectEvents) | |||
{ | |||
const ScopedLock sl (lock); | |||
for (const auto metadata : buffer) | |||
processNextMidiEvent (metadata.getMessage()); | |||
if (injectIndirectEvents) | |||
{ | |||
const int firstEventToAdd = eventsToAdd.getFirstEventTime(); | |||
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); | |||
for (const auto metadata : eventsToAdd) | |||
{ | |||
const auto pos = jlimit (0, numSamples - 1, roundToInt ((metadata.samplePosition - firstEventToAdd) * scaleFactor)); | |||
buffer.addEvent (metadata.getMessage(), startSample + pos); | |||
} | |||
} | |||
eventsToAdd.clear(); | |||
} | |||
//============================================================================== | |||
void MidiKeyboardState::addListener (Listener* listener) | |||
{ | |||
const ScopedLock sl (lock); | |||
listeners.add (listener); | |||
} | |||
void MidiKeyboardState::removeListener (Listener* listener) | |||
{ | |||
const ScopedLock sl (lock); | |||
listeners.remove (listener); | |||
} | |||
} // namespace juce |
@@ -1,195 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Represents a piano keyboard, keeping track of which keys are currently pressed. | |||
This object can parse a stream of midi events, using them to update its idea | |||
of which keys are pressed for each individual midi channel. | |||
When keys go up or down, it can broadcast these events to listener objects. | |||
It also allows key up/down events to be triggered with its noteOn() and noteOff() | |||
methods, and midi messages for these events will be merged into the | |||
midi stream that gets processed by processNextMidiBuffer(). | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiKeyboardState | |||
{ | |||
public: | |||
//============================================================================== | |||
MidiKeyboardState(); | |||
//============================================================================== | |||
/** Resets the state of the object. | |||
All internal data for all the channels is reset, but no events are sent as a | |||
result. | |||
If you want to release any keys that are currently down, and to send out note-up | |||
midi messages for this, use the allNotesOff() method instead. | |||
*/ | |||
void reset(); | |||
/** Returns true if the given midi key is currently held down for the given midi channel. | |||
The channel number must be between 1 and 16. If you want to see if any notes are | |||
on for a range of channels, use the isNoteOnForChannels() method. | |||
*/ | |||
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept; | |||
/** Returns true if the given midi key is currently held down on any of a set of midi channels. | |||
The channel mask has a bit set for each midi channel you want to test for - bit | |||
0 = midi channel 1, bit 1 = midi channel 2, etc. | |||
If a note is on for at least one of the specified channels, this returns true. | |||
*/ | |||
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept; | |||
/** Turns a specified note on. | |||
This will cause a suitable midi note-on event to be injected into the midi buffer during the | |||
next call to processNextMidiBuffer(). | |||
It will also trigger a synchronous callback to the listeners to tell them that the key has | |||
gone down. | |||
*/ | |||
void noteOn (int midiChannel, int midiNoteNumber, float velocity); | |||
/** Turns a specified note off. | |||
This will cause a suitable midi note-off event to be injected into the midi buffer during the | |||
next call to processNextMidiBuffer(). | |||
It will also trigger a synchronous callback to the listeners to tell them that the key has | |||
gone up. | |||
But if the note isn't actually down for the given channel, this method will in fact do nothing. | |||
*/ | |||
void noteOff (int midiChannel, int midiNoteNumber, float velocity); | |||
/** This will turn off any currently-down notes for the given midi channel. | |||
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels. | |||
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks | |||
and events being added to the midi stream. | |||
*/ | |||
void allNotesOff (int midiChannel); | |||
//============================================================================== | |||
/** Looks at a key-up/down event and uses it to update the state of this object. | |||
To process a buffer full of midi messages, use the processNextMidiBuffer() method | |||
instead. | |||
*/ | |||
void processNextMidiEvent (const MidiMessage& message); | |||
/** Scans a midi stream for up/down events and adds its own events to it. | |||
This will look for any up/down events and use them to update the internal state, | |||
synchronously making suitable callbacks to the listeners. | |||
If injectIndirectEvents is true, then midi events to produce the recent noteOn() | |||
and noteOff() calls will be added into the buffer. | |||
Only the section of the buffer whose timestamps are between startSample and | |||
(startSample + numSamples) will be affected, and any events added will be placed | |||
between these times. | |||
If you're going to use this method, you'll need to keep calling it regularly for | |||
it to work satisfactorily. | |||
To process a single midi event at a time, use the processNextMidiEvent() method | |||
instead. | |||
*/ | |||
void processNextMidiBuffer (MidiBuffer& buffer, | |||
int startSample, | |||
int numSamples, | |||
bool injectIndirectEvents); | |||
//============================================================================== | |||
/** Receives events from a MidiKeyboardState object. */ | |||
class JUCE_API Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
virtual ~Listener() = default; | |||
//============================================================================== | |||
/** Called when one of the MidiKeyboardState's keys is pressed. | |||
This will be called synchronously when the state is either processing a | |||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||
when a note is being played with its MidiKeyboardState::noteOn() method. | |||
Note that this callback could happen from an audio callback thread, so be | |||
careful not to block, and avoid any UI activity in the callback. | |||
*/ | |||
virtual void handleNoteOn (MidiKeyboardState* source, | |||
int midiChannel, int midiNoteNumber, float velocity) = 0; | |||
/** Called when one of the MidiKeyboardState's keys is released. | |||
This will be called synchronously when the state is either processing a | |||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||
when a note is being played with its MidiKeyboardState::noteOff() method. | |||
Note that this callback could happen from an audio callback thread, so be | |||
careful not to block, and avoid any UI activity in the callback. | |||
*/ | |||
virtual void handleNoteOff (MidiKeyboardState* source, | |||
int midiChannel, int midiNoteNumber, float velocity) = 0; | |||
}; | |||
/** Registers a listener for callbacks when keys go up or down. | |||
@see removeListener | |||
*/ | |||
void addListener (Listener* listener); | |||
/** Deregisters a listener. | |||
@see addListener | |||
*/ | |||
void removeListener (Listener* listener); | |||
private: | |||
//============================================================================== | |||
CriticalSection lock; | |||
std::atomic<uint16> noteStates[128]; | |||
MidiBuffer eventsToAdd; | |||
ListenerList<Listener> listeners; | |||
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity); | |||
void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) | |||
}; | |||
using MidiKeyboardStateListener = MidiKeyboardState::Listener; | |||
} // namespace juce |
@@ -1,986 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Encapsulates a MIDI message. | |||
@see MidiMessageSequence, MidiOutput, MidiInput | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiMessage | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a 3-byte short midi message. | |||
@param byte1 message byte 1 | |||
@param byte2 message byte 2 | |||
@param byte3 message byte 3 | |||
@param timeStamp the time to give the midi message - this value doesn't | |||
use any particular units, so will be application-specific | |||
*/ | |||
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept; | |||
/** Creates a 2-byte short midi message. | |||
@param byte1 message byte 1 | |||
@param byte2 message byte 2 | |||
@param timeStamp the time to give the midi message - this value doesn't | |||
use any particular units, so will be application-specific | |||
*/ | |||
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept; | |||
/** Creates a 1-byte short midi message. | |||
@param byte1 message byte 1 | |||
@param timeStamp the time to give the midi message - this value doesn't | |||
use any particular units, so will be application-specific | |||
*/ | |||
MidiMessage (int byte1, double timeStamp = 0) noexcept; | |||
/** Creates a midi message from a list of bytes. */ | |||
template <typename... Data> | |||
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes)) | |||
{ | |||
// this checks that the length matches the data.. | |||
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size); | |||
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... }; | |||
memcpy (allocateSpace (size), data, (size_t) size); | |||
} | |||
/** Creates a midi message from a block of data. */ | |||
MidiMessage (const void* data, int numBytes, double timeStamp = 0); | |||
/** Reads the next midi message from some data. | |||
This will read as many bytes from a data stream as it needs to make a | |||
complete message, and will return the number of bytes it used. This lets | |||
you read a sequence of midi messages from a file or stream. | |||
@param data the data to read from | |||
@param maxBytesToUse the maximum number of bytes it's allowed to read | |||
@param numBytesUsed returns the number of bytes that were actually needed | |||
@param lastStatusByte in a sequence of midi messages, the initial byte | |||
can be dropped from a message if it's the same as the | |||
first byte of the previous message, so this lets you | |||
supply the byte to use if the first byte of the message | |||
has in fact been dropped. | |||
@param timeStamp the time to give the midi message - this value doesn't | |||
use any particular units, so will be application-specific | |||
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether | |||
to expect the data to begin with a variable-length | |||
field indicating its size | |||
*/ | |||
MidiMessage (const void* data, int maxBytesToUse, | |||
int& numBytesUsed, uint8 lastStatusByte, | |||
double timeStamp = 0, | |||
bool sysexHasEmbeddedLength = true); | |||
/** Creates an empty sysex message. | |||
Since the MidiMessage has to contain a valid message, this default constructor | |||
just initialises it with an empty sysex message. | |||
*/ | |||
MidiMessage() noexcept; | |||
/** Creates a copy of another midi message. */ | |||
MidiMessage (const MidiMessage&); | |||
/** Creates a copy of another midi message, with a different timestamp. */ | |||
MidiMessage (const MidiMessage&, double newTimeStamp); | |||
/** Destructor. */ | |||
~MidiMessage() noexcept; | |||
/** Copies this message from another one. */ | |||
MidiMessage& operator= (const MidiMessage& other); | |||
/** Move constructor */ | |||
MidiMessage (MidiMessage&&) noexcept; | |||
/** Move assignment operator */ | |||
MidiMessage& operator= (MidiMessage&&) noexcept; | |||
//============================================================================== | |||
/** Returns a pointer to the raw midi data. | |||
@see getRawDataSize | |||
*/ | |||
const uint8* getRawData() const noexcept { return getData(); } | |||
/** Returns the number of bytes of data in the message. | |||
@see getRawData | |||
*/ | |||
int getRawDataSize() const noexcept { return size; } | |||
//============================================================================== | |||
/** Returns a human-readable description of the midi message as a string, | |||
for example "Note On C#3 Velocity 120 Channel 1". | |||
*/ | |||
String getDescription() const; | |||
//============================================================================== | |||
/** Returns the timestamp associated with this message. | |||
The exact meaning of this time and its units will vary, as messages are used in | |||
a variety of different contexts. | |||
If you're getting the message from a midi file, this could be a time in seconds, or | |||
a number of ticks - see MidiFile::convertTimestampTicksToSeconds(). | |||
If the message is being used in a MidiBuffer, it might indicate the number of | |||
audio samples from the start of the buffer. | |||
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage() | |||
for details of the way that it initialises this value. | |||
@see setTimeStamp, addToTimeStamp | |||
*/ | |||
double getTimeStamp() const noexcept { return timeStamp; } | |||
/** Changes the message's associated timestamp. | |||
The units for the timestamp will be application-specific - see the notes for getTimeStamp(). | |||
@see addToTimeStamp, getTimeStamp | |||
*/ | |||
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; } | |||
/** Adds a value to the message's timestamp. | |||
The units for the timestamp will be application-specific. | |||
*/ | |||
void addToTimeStamp (double delta) noexcept { timeStamp += delta; } | |||
/** Return a copy of this message with a new timestamp. | |||
The units for the timestamp will be application-specific - see the notes for getTimeStamp(). | |||
*/ | |||
MidiMessage withTimeStamp (double newTimestamp) const; | |||
//============================================================================== | |||
/** Returns the midi channel associated with the message. | |||
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g. | |||
if it's a sysex) | |||
@see isForChannel, setChannel | |||
*/ | |||
int getChannel() const noexcept; | |||
/** Returns true if the message applies to the given midi channel. | |||
@param channelNumber the channel number to look for, in the range 1 to 16 | |||
@see getChannel, setChannel | |||
*/ | |||
bool isForChannel (int channelNumber) const noexcept; | |||
/** Changes the message's midi channel. | |||
This won't do anything for non-channel messages like sysexes. | |||
@param newChannelNumber the channel number to change it to, in the range 1 to 16 | |||
*/ | |||
void setChannel (int newChannelNumber) noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a system-exclusive message. | |||
*/ | |||
bool isSysEx() const noexcept; | |||
/** Returns a pointer to the sysex data inside the message. | |||
If this event isn't a sysex event, it'll return 0. | |||
@see getSysExDataSize | |||
*/ | |||
const uint8* getSysExData() const noexcept; | |||
/** Returns the size of the sysex data. | |||
This value excludes the 0xf0 header byte and the 0xf7 at the end. | |||
@see getSysExData | |||
*/ | |||
int getSysExDataSize() const noexcept; | |||
//============================================================================== | |||
/** Returns true if this message is a 'key-down' event. | |||
@param returnTrueForVelocity0 if true, then if this event is a note-on with | |||
velocity 0, it will still be considered to be a note-on and the | |||
method will return true. If returnTrueForVelocity0 is false, then | |||
if this is a note-on event with velocity 0, it'll be regarded as | |||
a note-off, and the method will return false | |||
@see isNoteOff, getNoteNumber, getVelocity, noteOn | |||
*/ | |||
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept; | |||
/** Creates a key-down message (using a floating-point velocity). | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@param velocity in the range 0 to 1.0 | |||
@see isNoteOn | |||
*/ | |||
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept; | |||
/** Creates a key-down message (using an integer velocity). | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@param velocity in the range 0 to 127 | |||
@see isNoteOn | |||
*/ | |||
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept; | |||
/** Returns true if this message is a 'key-up' event. | |||
If returnTrueForNoteOnVelocity0 is true, then his will also return true | |||
for a note-on event with a velocity of 0. | |||
@see isNoteOn, getNoteNumber, getVelocity, noteOff | |||
*/ | |||
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept; | |||
/** Creates a key-up message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@param velocity in the range 0 to 1.0 | |||
@see isNoteOff | |||
*/ | |||
static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept; | |||
/** Creates a key-up message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@param velocity in the range 0 to 127 | |||
@see isNoteOff | |||
*/ | |||
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept; | |||
/** Creates a key-up message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@see isNoteOff | |||
*/ | |||
static MidiMessage noteOff (int channel, int noteNumber) noexcept; | |||
/** Returns true if this message is a 'key-down' or 'key-up' event. | |||
@see isNoteOn, isNoteOff | |||
*/ | |||
bool isNoteOnOrOff() const noexcept; | |||
/** Returns the midi note number for note-on and note-off messages. | |||
If the message isn't a note-on or off, the value returned is undefined. | |||
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||
*/ | |||
int getNoteNumber() const noexcept; | |||
/** Changes the midi note number of a note-on or note-off message. | |||
If the message isn't a note on or off, this will do nothing. | |||
*/ | |||
void setNoteNumber (int newNoteNumber) noexcept; | |||
//============================================================================== | |||
/** Returns the velocity of a note-on or note-off message. | |||
The value returned will be in the range 0 to 127. | |||
If the message isn't a note-on or off event, it will return 0. | |||
@see getFloatVelocity | |||
*/ | |||
uint8 getVelocity() const noexcept; | |||
/** Returns the velocity of a note-on or note-off message. | |||
The value returned will be in the range 0 to 1.0 | |||
If the message isn't a note-on or off event, it will return 0. | |||
@see getVelocity, setVelocity | |||
*/ | |||
float getFloatVelocity() const noexcept; | |||
/** Changes the velocity of a note-on or note-off message. | |||
If the message isn't a note on or off, this will do nothing. | |||
@param newVelocity the new velocity, in the range 0 to 1.0 | |||
@see getFloatVelocity, multiplyVelocity | |||
*/ | |||
void setVelocity (float newVelocity) noexcept; | |||
/** Multiplies the velocity of a note-on or note-off message by a given amount. | |||
If the message isn't a note on or off, this will do nothing. | |||
@param scaleFactor the value by which to multiply the velocity | |||
@see setVelocity | |||
*/ | |||
void multiplyVelocity (float scaleFactor) noexcept; | |||
//============================================================================== | |||
/** Returns true if this message is a 'sustain pedal down' controller message. */ | |||
bool isSustainPedalOn() const noexcept; | |||
/** Returns true if this message is a 'sustain pedal up' controller message. */ | |||
bool isSustainPedalOff() const noexcept; | |||
/** Returns true if this message is a 'sostenuto pedal down' controller message. */ | |||
bool isSostenutoPedalOn() const noexcept; | |||
/** Returns true if this message is a 'sostenuto pedal up' controller message. */ | |||
bool isSostenutoPedalOff() const noexcept; | |||
/** Returns true if this message is a 'soft pedal down' controller message. */ | |||
bool isSoftPedalOn() const noexcept; | |||
/** Returns true if this message is a 'soft pedal up' controller message. */ | |||
bool isSoftPedalOff() const noexcept; | |||
//============================================================================== | |||
/** Returns true if the message is a program (patch) change message. | |||
@see getProgramChangeNumber, getGMInstrumentName | |||
*/ | |||
bool isProgramChange() const noexcept; | |||
/** Returns the new program number of a program change message. | |||
If the message isn't a program change, the value returned is undefined. | |||
@see isProgramChange, getGMInstrumentName | |||
*/ | |||
int getProgramChangeNumber() const noexcept; | |||
/** Creates a program-change message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param programNumber the midi program number, 0 to 127 | |||
@see isProgramChange, getGMInstrumentName | |||
*/ | |||
static MidiMessage programChange (int channel, int programNumber) noexcept; | |||
//============================================================================== | |||
/** Returns true if the message is a pitch-wheel move. | |||
@see getPitchWheelValue, pitchWheel | |||
*/ | |||
bool isPitchWheel() const noexcept; | |||
/** Returns the pitch wheel position from a pitch-wheel move message. | |||
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. | |||
If called for messages which aren't pitch wheel events, the number returned will be | |||
nonsense. | |||
@see isPitchWheel | |||
*/ | |||
int getPitchWheelValue() const noexcept; | |||
/** Creates a pitch-wheel move message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param position the wheel position, in the range 0 to 16383 | |||
@see isPitchWheel | |||
*/ | |||
static MidiMessage pitchWheel (int channel, int position) noexcept; | |||
//============================================================================== | |||
/** Returns true if the message is an aftertouch event. | |||
For aftertouch events, use the getNoteNumber() method to find out the key | |||
that it applies to, and getAfterTouchValue() to find out the amount. Use | |||
getChannel() to find out the channel. | |||
@see getAftertouchValue, getNoteNumber | |||
*/ | |||
bool isAftertouch() const noexcept; | |||
/** Returns the amount of aftertouch from an aftertouch messages. | |||
The value returned is in the range 0 to 127, and will be nonsense for messages | |||
other than aftertouch messages. | |||
@see isAftertouch | |||
*/ | |||
int getAfterTouchValue() const noexcept; | |||
/** Creates an aftertouch message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param noteNumber the key number, 0 to 127 | |||
@param aftertouchAmount the amount of aftertouch, 0 to 127 | |||
@see isAftertouch | |||
*/ | |||
static MidiMessage aftertouchChange (int channel, | |||
int noteNumber, | |||
int aftertouchAmount) noexcept; | |||
/** Returns true if the message is a channel-pressure change event. | |||
This is like aftertouch, but common to the whole channel rather than a specific | |||
note. Use getChannelPressureValue() to find out the pressure, and getChannel() | |||
to find out the channel. | |||
@see channelPressureChange | |||
*/ | |||
bool isChannelPressure() const noexcept; | |||
/** Returns the pressure from a channel pressure change message. | |||
@returns the pressure, in the range 0 to 127 | |||
@see isChannelPressure, channelPressureChange | |||
*/ | |||
int getChannelPressureValue() const noexcept; | |||
/** Creates a channel-pressure change event. | |||
@param channel the midi channel: 1 to 16 | |||
@param pressure the pressure, 0 to 127 | |||
@see isChannelPressure | |||
*/ | |||
static MidiMessage channelPressureChange (int channel, int pressure) noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a midi controller message. | |||
@see getControllerNumber, getControllerValue, controllerEvent | |||
*/ | |||
bool isController() const noexcept; | |||
/** Returns the controller number of a controller message. | |||
The name of the controller can be looked up using the getControllerName() method. | |||
Note that the value returned is invalid for messages that aren't controller changes. | |||
@see isController, getControllerName, getControllerValue | |||
*/ | |||
int getControllerNumber() const noexcept; | |||
/** Returns the controller value from a controller message. | |||
A value 0 to 127 is returned to indicate the new controller position. | |||
Note that the value returned is invalid for messages that aren't controller changes. | |||
@see isController, getControllerNumber | |||
*/ | |||
int getControllerValue() const noexcept; | |||
/** Returns true if this message is a controller message and if it has the specified | |||
controller type. | |||
*/ | |||
bool isControllerOfType (int controllerType) const noexcept; | |||
/** Creates a controller message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@param controllerType the type of controller | |||
@param value the controller value | |||
@see isController | |||
*/ | |||
static MidiMessage controllerEvent (int channel, | |||
int controllerType, | |||
int value) noexcept; | |||
/** Checks whether this message is an all-notes-off message. | |||
@see allNotesOff | |||
*/ | |||
bool isAllNotesOff() const noexcept; | |||
/** Checks whether this message is an all-sound-off message. | |||
@see allSoundOff | |||
*/ | |||
bool isAllSoundOff() const noexcept; | |||
/** Checks whether this message is a reset all controllers message. | |||
@see allControllerOff | |||
*/ | |||
bool isResetAllControllers() const noexcept; | |||
/** Creates an all-notes-off message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@see isAllNotesOff | |||
*/ | |||
static MidiMessage allNotesOff (int channel) noexcept; | |||
/** Creates an all-sound-off message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@see isAllSoundOff | |||
*/ | |||
static MidiMessage allSoundOff (int channel) noexcept; | |||
/** Creates an all-controllers-off message. | |||
@param channel the midi channel, in the range 1 to 16 | |||
*/ | |||
static MidiMessage allControllersOff (int channel) noexcept; | |||
//============================================================================== | |||
/** Returns true if this event is a meta-event. | |||
Meta-events are things like tempo changes, track names, etc. | |||
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||
*/ | |||
bool isMetaEvent() const noexcept; | |||
/** Returns a meta-event's type number. | |||
If the message isn't a meta-event, this will return -1. | |||
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||
*/ | |||
int getMetaEventType() const noexcept; | |||
/** Returns a pointer to the data in a meta-event. | |||
@see isMetaEvent, getMetaEventLength | |||
*/ | |||
const uint8* getMetaEventData() const noexcept; | |||
/** Returns the length of the data for a meta-event. | |||
@see isMetaEvent, getMetaEventData | |||
*/ | |||
int getMetaEventLength() const noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a 'track' meta-event. */ | |||
bool isTrackMetaEvent() const noexcept; | |||
/** Returns true if this is an 'end-of-track' meta-event. */ | |||
bool isEndOfTrackMetaEvent() const noexcept; | |||
/** Creates an end-of-track meta-event. | |||
@see isEndOfTrackMetaEvent | |||
*/ | |||
static MidiMessage endOfTrack() noexcept; | |||
/** Returns true if this is an 'track name' meta-event. | |||
You can use the getTextFromTextMetaEvent() method to get the track's name. | |||
*/ | |||
bool isTrackNameEvent() const noexcept; | |||
/** Returns true if this is a 'text' meta-event. | |||
@see getTextFromTextMetaEvent | |||
*/ | |||
bool isTextMetaEvent() const noexcept; | |||
/** Returns the text from a text meta-event. | |||
@see isTextMetaEvent | |||
*/ | |||
String getTextFromTextMetaEvent() const; | |||
/** Creates a text meta-event. */ | |||
static MidiMessage textMetaEvent (int type, StringRef text); | |||
//============================================================================== | |||
/** Returns true if this is a 'tempo' meta-event. | |||
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote | |||
*/ | |||
bool isTempoMetaEvent() const noexcept; | |||
/** Returns the tick length from a tempo meta-event. | |||
@param timeFormat the 16-bit time format value from the midi file's header. | |||
@returns the tick length (in seconds). | |||
@see isTempoMetaEvent | |||
*/ | |||
double getTempoMetaEventTickLength (short timeFormat) const noexcept; | |||
/** Calculates the seconds-per-quarter-note from a tempo meta-event. | |||
@see isTempoMetaEvent, getTempoMetaEventTickLength | |||
*/ | |||
double getTempoSecondsPerQuarterNote() const noexcept; | |||
/** Creates a tempo meta-event. | |||
@see isTempoMetaEvent | |||
*/ | |||
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a 'time-signature' meta-event. | |||
@see getTimeSignatureInfo | |||
*/ | |||
bool isTimeSignatureMetaEvent() const noexcept; | |||
/** Returns the time-signature values from a time-signature meta-event. | |||
@see isTimeSignatureMetaEvent | |||
*/ | |||
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; | |||
/** Creates a time-signature meta-event. | |||
@see isTimeSignatureMetaEvent | |||
*/ | |||
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); | |||
//============================================================================== | |||
/** Returns true if this is a 'key-signature' meta-event. | |||
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey | |||
*/ | |||
bool isKeySignatureMetaEvent() const noexcept; | |||
/** Returns the key from a key-signature meta-event. | |||
This method must only be called if isKeySignatureMetaEvent() is true. | |||
A positive number here indicates the number of sharps in the key signature, | |||
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, | |||
-2 = Bb + Eb | |||
@see isKeySignatureMetaEvent, isKeySignatureMajorKey | |||
*/ | |||
int getKeySignatureNumberOfSharpsOrFlats() const noexcept; | |||
/** Returns true if this key-signature event is major, or false if it's minor. | |||
This method must only be called if isKeySignatureMetaEvent() is true. | |||
*/ | |||
bool isKeySignatureMajorKey() const noexcept; | |||
/** Creates a key-signature meta-event. | |||
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps | |||
in the key; if negative, the number of flats | |||
@param isMinorKey if true, the key is minor; if false, it is major | |||
@see isKeySignatureMetaEvent | |||
*/ | |||
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); | |||
//============================================================================== | |||
/** Returns true if this is a 'channel' meta-event. | |||
A channel meta-event specifies the midi channel that should be used | |||
for subsequent meta-events. | |||
@see getMidiChannelMetaEventChannel | |||
*/ | |||
bool isMidiChannelMetaEvent() const noexcept; | |||
/** Returns the channel number from a channel meta-event. | |||
@returns the channel, in the range 1 to 16. | |||
@see isMidiChannelMetaEvent | |||
*/ | |||
int getMidiChannelMetaEventChannel() const noexcept; | |||
/** Creates a midi channel meta-event. | |||
@param channel the midi channel, in the range 1 to 16 | |||
@see isMidiChannelMetaEvent | |||
*/ | |||
static MidiMessage midiChannelMetaEvent (int channel) noexcept; | |||
//============================================================================== | |||
/** Returns true if this is an active-sense message. */ | |||
bool isActiveSense() const noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a midi start event. | |||
@see midiStart | |||
*/ | |||
bool isMidiStart() const noexcept; | |||
/** Creates a midi start event. */ | |||
static MidiMessage midiStart() noexcept; | |||
/** Returns true if this is a midi continue event. | |||
@see midiContinue | |||
*/ | |||
bool isMidiContinue() const noexcept; | |||
/** Creates a midi continue event. */ | |||
static MidiMessage midiContinue() noexcept; | |||
/** Returns true if this is a midi stop event. | |||
@see midiStop | |||
*/ | |||
bool isMidiStop() const noexcept; | |||
/** Creates a midi stop event. */ | |||
static MidiMessage midiStop() noexcept; | |||
/** Returns true if this is a midi clock event. | |||
@see midiClock, songPositionPointer | |||
*/ | |||
bool isMidiClock() const noexcept; | |||
/** Creates a midi clock event. */ | |||
static MidiMessage midiClock() noexcept; | |||
/** Returns true if this is a song-position-pointer message. | |||
@see getSongPositionPointerMidiBeat, songPositionPointer | |||
*/ | |||
bool isSongPositionPointer() const noexcept; | |||
/** Returns the midi beat-number of a song-position-pointer message. | |||
@see isSongPositionPointer, songPositionPointer | |||
*/ | |||
int getSongPositionPointerMidiBeat() const noexcept; | |||
/** Creates a song-position-pointer message. | |||
The position is a number of midi beats from the start of the song, where 1 midi | |||
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there | |||
are 4 midi beats in a quarter-note. | |||
@see isSongPositionPointer, getSongPositionPointerMidiBeat | |||
*/ | |||
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; | |||
//============================================================================== | |||
/** Returns true if this is a quarter-frame midi timecode message. | |||
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue | |||
*/ | |||
bool isQuarterFrame() const noexcept; | |||
/** Returns the sequence number of a quarter-frame midi timecode message. | |||
This will be a value between 0 and 7. | |||
@see isQuarterFrame, getQuarterFrameValue, quarterFrame | |||
*/ | |||
int getQuarterFrameSequenceNumber() const noexcept; | |||
/** Returns the value from a quarter-frame message. | |||
This will be the lower nybble of the message's data-byte, a value between 0 and 15 | |||
*/ | |||
int getQuarterFrameValue() const noexcept; | |||
/** Creates a quarter-frame MTC message. | |||
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte | |||
@param value a value 0 to 15 for the lower nybble of the message's data byte | |||
*/ | |||
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; | |||
/** SMPTE timecode types. | |||
Used by the getFullFrameParameters() and fullFrame() methods. | |||
*/ | |||
enum SmpteTimecodeType | |||
{ | |||
fps24 = 0, | |||
fps25 = 1, | |||
fps30drop = 2, | |||
fps30 = 3 | |||
}; | |||
/** Returns true if this is a full-frame midi timecode message. */ | |||
bool isFullFrame() const noexcept; | |||
/** Extracts the timecode information from a full-frame midi timecode message. | |||
You should only call this on messages where you've used isFullFrame() to | |||
check that they're the right kind. | |||
*/ | |||
void getFullFrameParameters (int& hours, | |||
int& minutes, | |||
int& seconds, | |||
int& frames, | |||
SmpteTimecodeType& timecodeType) const noexcept; | |||
/** Creates a full-frame MTC message. */ | |||
static MidiMessage fullFrame (int hours, | |||
int minutes, | |||
int seconds, | |||
int frames, | |||
SmpteTimecodeType timecodeType); | |||
//============================================================================== | |||
/** Types of MMC command. | |||
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand | |||
*/ | |||
enum MidiMachineControlCommand | |||
{ | |||
mmc_stop = 1, | |||
mmc_play = 2, | |||
mmc_deferredplay = 3, | |||
mmc_fastforward = 4, | |||
mmc_rewind = 5, | |||
mmc_recordStart = 6, | |||
mmc_recordStop = 7, | |||
mmc_pause = 9 | |||
}; | |||
/** Checks whether this is an MMC message. | |||
If it is, you can use the getMidiMachineControlCommand() to find out its type. | |||
*/ | |||
bool isMidiMachineControlMessage() const noexcept; | |||
/** For an MMC message, this returns its type. | |||
Make sure it's actually an MMC message with isMidiMachineControlMessage() before | |||
calling this method. | |||
*/ | |||
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; | |||
/** Creates an MMC message. */ | |||
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); | |||
/** Checks whether this is an MMC "goto" message. | |||
If it is, the parameters passed-in are set to the time that the message contains. | |||
@see midiMachineControlGoto | |||
*/ | |||
bool isMidiMachineControlGoto (int& hours, | |||
int& minutes, | |||
int& seconds, | |||
int& frames) const noexcept; | |||
/** Creates an MMC "goto" message. | |||
This messages tells the device to go to a specific frame. | |||
@see isMidiMachineControlGoto | |||
*/ | |||
static MidiMessage midiMachineControlGoto (int hours, | |||
int minutes, | |||
int seconds, | |||
int frames); | |||
//============================================================================== | |||
/** Creates a master-volume change message. | |||
@param volume the volume, 0 to 1.0 | |||
*/ | |||
static MidiMessage masterVolume (float volume); | |||
//============================================================================== | |||
/** Creates a system-exclusive message. | |||
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. | |||
*/ | |||
static MidiMessage createSysExMessage (const void* sysexData, | |||
int dataSize); | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
/** Reads a midi variable-length integer. | |||
The `data` argument indicates the data to read the number from, | |||
and `numBytesUsed` is used as an out-parameter to indicate the number | |||
of bytes that were read. | |||
*/ | |||
[[deprecated ("This signature has been deprecated in favour of the safer readVariableLengthValue.")]] | |||
static int readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept; | |||
#endif | |||
/** Holds information about a variable-length value which was parsed | |||
from a stream of bytes. | |||
A valid value requires that `bytesUsed` is greater than 0. | |||
*/ | |||
struct VariableLengthValue | |||
{ | |||
VariableLengthValue() = default; | |||
VariableLengthValue (int valueIn, int bytesUsedIn) | |||
: value (valueIn), bytesUsed (bytesUsedIn) {} | |||
bool isValid() const noexcept { return bytesUsed > 0; } | |||
int value = 0; | |||
int bytesUsed = 0; | |||
}; | |||
/** Reads a midi variable-length integer, with protection against buffer overflow. | |||
@param data the data to read the number from | |||
@param maxBytesToUse the number of bytes in the region following `data` | |||
@returns a struct containing the parsed value, and the number | |||
of bytes that were read. If parsing fails, both the | |||
`value` and `bytesUsed` fields will be set to 0 and | |||
`isValid()` will return false | |||
*/ | |||
static VariableLengthValue readVariableLengthValue (const uint8* data, | |||
int maxBytesToUse) noexcept; | |||
/** Based on the first byte of a short midi message, this uses a lookup table | |||
to return the message length (either 1, 2, or 3 bytes). | |||
The value passed in must be 0x80 or higher. | |||
*/ | |||
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; | |||
//============================================================================== | |||
/** Returns the name of a midi note number. | |||
E.g "C", "D#", etc. | |||
@param noteNumber the midi note number, 0 to 127 | |||
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise | |||
they'll be flattened, e.g. "Db" | |||
@param includeOctaveNumber if true, the octave number will be appended to the string, | |||
e.g. "C#4" | |||
@param octaveNumForMiddleC if an octave number is being appended, this indicates the | |||
number that will be used for middle C's octave | |||
@see getMidiNoteInHertz | |||
*/ | |||
static String getMidiNoteName (int noteNumber, | |||
bool useSharps, | |||
bool includeOctaveNumber, | |||
int octaveNumForMiddleC); | |||
/** Returns the frequency of a midi note number. | |||
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | |||
@see getMidiNoteName | |||
*/ | |||
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; | |||
/** Returns true if the given midi note number is a black key. */ | |||
static bool isMidiNoteBlack (int noteNumber) noexcept; | |||
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index. | |||
@param midiInstrumentNumber the program number 0 to 127 | |||
@see getProgramChangeNumber | |||
*/ | |||
static const char* getGMInstrumentName (int midiInstrumentNumber); | |||
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. | |||
@param midiBankNumber the bank, 0 to 15 | |||
*/ | |||
static const char* getGMInstrumentBankName (int midiBankNumber); | |||
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. | |||
@param midiNoteNumber the key number, 35 to 81 | |||
*/ | |||
static const char* getRhythmInstrumentName (int midiNoteNumber); | |||
/** Returns the name of a controller type number, or nullptr if unknown for this controller number. | |||
@see getControllerNumber | |||
*/ | |||
static const char* getControllerName (int controllerNumber); | |||
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ | |||
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; | |||
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ | |||
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, | |||
float pitchbendRangeInSemitones) noexcept; | |||
private: | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
union PackedData | |||
{ | |||
uint8* allocatedData; | |||
uint8 asBytes[sizeof (uint8*)]; | |||
}; | |||
PackedData packedData; | |||
double timeStamp = 0; | |||
int size; | |||
#endif | |||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : const_cast<uint8*>(packedData.asBytes); } | |||
uint8* allocateSpace (int); | |||
}; | |||
} // namespace juce |
@@ -1,870 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} | |||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {} | |||
//============================================================================== | |||
MidiMessageSequence::MidiMessageSequence() | |||
{ | |||
} | |||
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) | |||
{ | |||
list.addCopiesOf (other.list); | |||
for (int i = 0; i < list.size(); ++i) | |||
{ | |||
auto noteOffIndex = other.getIndexOfMatchingKeyUp (i); | |||
if (noteOffIndex >= 0) | |||
list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex); | |||
} | |||
} | |||
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) | |||
{ | |||
MidiMessageSequence otherCopy (other); | |||
swapWith (otherCopy); | |||
return *this; | |||
} | |||
MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept | |||
: list (std::move (other.list)) | |||
{ | |||
} | |||
MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept | |||
{ | |||
list = std::move (other.list); | |||
return *this; | |||
} | |||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept | |||
{ | |||
list.swapWith (other.list); | |||
} | |||
void MidiMessageSequence::clear() | |||
{ | |||
list.clear(); | |||
} | |||
int MidiMessageSequence::getNumEvents() const noexcept | |||
{ | |||
return list.size(); | |||
} | |||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept | |||
{ | |||
return list[index]; | |||
} | |||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); } | |||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); } | |||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); } | |||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); } | |||
double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept | |||
{ | |||
if (auto* meh = list[index]) | |||
if (auto* noteOff = meh->noteOffObject) | |||
return noteOff->message.getTimeStamp(); | |||
return 0; | |||
} | |||
int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept | |||
{ | |||
if (auto* meh = list[index]) | |||
{ | |||
if (auto* noteOff = meh->noteOffObject) | |||
{ | |||
for (int i = index; i < list.size(); ++i) | |||
if (list.getUnchecked(i) == noteOff) | |||
return i; | |||
jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence | |||
} | |||
} | |||
return -1; | |||
} | |||
int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept | |||
{ | |||
return list.indexOf (event); | |||
} | |||
int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept | |||
{ | |||
auto numEvents = list.size(); | |||
int i; | |||
for (i = 0; i < numEvents; ++i) | |||
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) | |||
break; | |||
return i; | |||
} | |||
//============================================================================== | |||
double MidiMessageSequence::getStartTime() const noexcept | |||
{ | |||
return getEventTime (0); | |||
} | |||
double MidiMessageSequence::getEndTime() const noexcept | |||
{ | |||
return getEventTime (list.size() - 1); | |||
} | |||
double MidiMessageSequence::getEventTime (const int index) const noexcept | |||
{ | |||
if (auto* meh = list[index]) | |||
return meh->message.getTimeStamp(); | |||
return 0; | |||
} | |||
//============================================================================== | |||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment) | |||
{ | |||
newEvent->message.addToTimeStamp (timeAdjustment); | |||
auto time = newEvent->message.getTimeStamp(); | |||
int i; | |||
for (i = list.size(); --i >= 0;) | |||
if (list.getUnchecked(i)->message.getTimeStamp() <= time) | |||
break; | |||
list.insert (i + 1, newEvent); | |||
return newEvent; | |||
} | |||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) | |||
{ | |||
return addEvent (new MidiEventHolder (newMessage), timeAdjustment); | |||
} | |||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment) | |||
{ | |||
return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment); | |||
} | |||
void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) | |||
{ | |||
if (isPositiveAndBelow (index, list.size())) | |||
{ | |||
if (deleteMatchingNoteUp) | |||
deleteEvent (getIndexOfMatchingKeyUp (index), false); | |||
list.remove (index); | |||
} | |||
} | |||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) | |||
{ | |||
for (auto* m : other) | |||
{ | |||
auto newOne = new MidiEventHolder (m->message); | |||
newOne->message.addToTimeStamp (timeAdjustment); | |||
list.add (newOne); | |||
} | |||
sort(); | |||
} | |||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||
double timeAdjustment, | |||
double firstAllowableTime, | |||
double endOfAllowableDestTimes) | |||
{ | |||
for (auto* m : other) | |||
{ | |||
auto t = m->message.getTimeStamp() + timeAdjustment; | |||
if (t >= firstAllowableTime && t < endOfAllowableDestTimes) | |||
{ | |||
auto newOne = new MidiEventHolder (m->message); | |||
newOne->message.setTimeStamp (t); | |||
list.add (newOne); | |||
} | |||
} | |||
sort(); | |||
} | |||
void MidiMessageSequence::sort() noexcept | |||
{ | |||
std::stable_sort (list.begin(), list.end(), | |||
[] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); }); | |||
} | |||
void MidiMessageSequence::updateMatchedPairs() noexcept | |||
{ | |||
for (int i = 0; i < list.size(); ++i) | |||
{ | |||
auto* meh = list.getUnchecked(i); | |||
auto& m1 = meh->message; | |||
if (m1.isNoteOn()) | |||
{ | |||
meh->noteOffObject = nullptr; | |||
auto note = m1.getNoteNumber(); | |||
auto chan = m1.getChannel(); | |||
auto len = list.size(); | |||
for (int j = i + 1; j < len; ++j) | |||
{ | |||
auto* meh2 = list.getUnchecked(j); | |||
auto& m = meh2->message; | |||
if (m.getNoteNumber() == note && m.getChannel() == chan) | |||
{ | |||
if (m.isNoteOff()) | |||
{ | |||
meh->noteOffObject = meh2; | |||
break; | |||
} | |||
if (m.isNoteOn()) | |||
{ | |||
auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); | |||
list.insert (j, newEvent); | |||
newEvent->message.setTimeStamp (m.getTimeStamp()); | |||
meh->noteOffObject = newEvent; | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
void MidiMessageSequence::addTimeToMessages (double delta) noexcept | |||
{ | |||
if (delta != 0) | |||
for (auto* m : list) | |||
m->message.addToTimeStamp (delta); | |||
} | |||
//============================================================================== | |||
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, | |||
MidiMessageSequence& destSequence, | |||
const bool alsoIncludeMetaEvents) const | |||
{ | |||
for (auto* meh : list) | |||
if (meh->message.isForChannel (channelNumberToExtract) | |||
|| (alsoIncludeMetaEvents && meh->message.isMetaEvent())) | |||
destSequence.addEvent (meh->message); | |||
} | |||
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const | |||
{ | |||
for (auto* meh : list) | |||
if (meh->message.isSysEx()) | |||
destSequence.addEvent (meh->message); | |||
} | |||
void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) | |||
{ | |||
for (int i = list.size(); --i >= 0;) | |||
if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) | |||
list.remove(i); | |||
} | |||
void MidiMessageSequence::deleteSysExMessages() | |||
{ | |||
for (int i = list.size(); --i >= 0;) | |||
if (list.getUnchecked(i)->message.isSysEx()) | |||
list.remove(i); | |||
} | |||
//============================================================================== | |||
class OptionalPitchWheel | |||
{ | |||
Optional<int> value; | |||
public: | |||
void emit (int channel, Array<MidiMessage>& out) const | |||
{ | |||
if (value.hasValue()) | |||
out.add (MidiMessage::pitchWheel (channel, *value)); | |||
} | |||
void set (int v) | |||
{ | |||
value = v; | |||
} | |||
}; | |||
class OptionalControllerValues | |||
{ | |||
Optional<char> values[128]; | |||
public: | |||
void emit (int channel, Array<MidiMessage>& out) const | |||
{ | |||
for (auto it = std::begin (values); it != std::end (values); ++it) | |||
if (it->hasValue()) | |||
out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), **it)); | |||
} | |||
void set (int controller, int value) | |||
{ | |||
values[controller] = (char) value; | |||
} | |||
}; | |||
class OptionalProgramChange | |||
{ | |||
Optional<char> value, bankLSB, bankMSB; | |||
public: | |||
void emit (int channel, double time, Array<MidiMessage>& out) const | |||
{ | |||
if (! value.hasValue()) | |||
return; | |||
if (bankLSB.hasValue() && bankMSB.hasValue()) | |||
{ | |||
out.add (MidiMessage::controllerEvent (channel, 0x00, *bankMSB).withTimeStamp (time)); | |||
out.add (MidiMessage::controllerEvent (channel, 0x20, *bankLSB).withTimeStamp (time)); | |||
} | |||
out.add (MidiMessage::programChange (channel, *value).withTimeStamp (time)); | |||
} | |||
// Returns true if this is a bank number change, and false otherwise. | |||
bool trySetBank (int controller, int v) | |||
{ | |||
switch (controller) | |||
{ | |||
case 0x00: bankMSB = (char) v; return true; | |||
case 0x20: bankLSB = (char) v; return true; | |||
} | |||
return false; | |||
} | |||
void setProgram (int v) { value = (char) v; } | |||
}; | |||
class ParameterNumberState | |||
{ | |||
enum class Kind { rpn, nrpn }; | |||
Optional<char> newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; | |||
Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn; | |||
public: | |||
// If the effective parameter number has changed since the last time this function was called, | |||
// this will emit the current parameter in full (MSB and LSB). | |||
// This should be called before each data message (entry, increment, decrement: 0x06, 0x26, 0x60, 0x61) | |||
// to ensure that the data message operates on the correct parameter number. | |||
void sendIfNecessary (int channel, double time, Array<MidiMessage>& out) | |||
{ | |||
const auto newestMsb = newestKind == Kind::rpn ? newestRpnMsb : newestNrpnMsb; | |||
const auto newestLsb = newestKind == Kind::rpn ? newestRpnLsb : newestNrpnLsb; | |||
auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb); | |||
const auto newest = std::tie (newestKind, newestMsb, newestLsb); | |||
if (lastSent == newest || ! newestMsb.hasValue() || ! newestLsb.hasValue()) | |||
return; | |||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, *newestMsb).withTimeStamp (time)); | |||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, *newestLsb).withTimeStamp (time)); | |||
lastSent = newest; | |||
} | |||
// Returns true if this is a parameter number change, and false otherwise. | |||
bool trySetProgramNumber (int controller, int value) | |||
{ | |||
switch (controller) | |||
{ | |||
case 0x65: newestRpnMsb = (char) value; newestKind = Kind::rpn; return true; | |||
case 0x64: newestRpnLsb = (char) value; newestKind = Kind::rpn; return true; | |||
case 0x63: newestNrpnMsb = (char) value; newestKind = Kind::nrpn; return true; | |||
case 0x62: newestNrpnLsb = (char) value; newestKind = Kind::nrpn; return true; | |||
} | |||
return false; | |||
} | |||
}; | |||
void MidiMessageSequence::createControllerUpdatesForTime (int channel, double time, Array<MidiMessage>& dest) | |||
{ | |||
OptionalProgramChange programChange; | |||
OptionalControllerValues controllers; | |||
OptionalPitchWheel pitchWheel; | |||
ParameterNumberState parameterNumberState; | |||
for (const auto& item : list) | |||
{ | |||
const auto& mm = item->message; | |||
if (! (mm.isForChannel (channel) && mm.getTimeStamp() <= time)) | |||
continue; | |||
if (mm.isController()) | |||
{ | |||
const auto num = mm.getControllerNumber(); | |||
if (parameterNumberState.trySetProgramNumber (num, mm.getControllerValue())) | |||
continue; | |||
if (programChange.trySetBank (num, mm.getControllerValue())) | |||
continue; | |||
constexpr int passthroughs[] { 0x06, 0x26, 0x60, 0x61 }; | |||
if (std::find (std::begin (passthroughs), std::end (passthroughs), num) != std::end (passthroughs)) | |||
{ | |||
parameterNumberState.sendIfNecessary (channel, mm.getTimeStamp(), dest); | |||
dest.add (mm); | |||
} | |||
else | |||
{ | |||
controllers.set (num, mm.getControllerValue()); | |||
} | |||
} | |||
else if (mm.isProgramChange()) | |||
{ | |||
programChange.setProgram (mm.getProgramChangeNumber()); | |||
} | |||
else if (mm.isPitchWheel()) | |||
{ | |||
pitchWheel.set (mm.getPitchWheelValue()); | |||
} | |||
} | |||
pitchWheel.emit (channel, dest); | |||
controllers.emit (channel, dest); | |||
// Also emits bank change messages if necessary. | |||
programChange.emit (channel, time, dest); | |||
// Set the parameter number to its final state. | |||
parameterNumberState.sendIfNecessary (channel, time, dest); | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiMessageSequenceTest : public UnitTest | |||
{ | |||
MidiMessageSequenceTest() | |||
: UnitTest ("MidiMessageSequence", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
MidiMessageSequence s; | |||
s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0)); | |||
s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0)); | |||
s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0)); | |||
s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0)); | |||
beginTest ("Start & end time"); | |||
expectEquals (s.getStartTime(), 0.0); | |||
expectEquals (s.getEndTime(), 8.0); | |||
expectEquals (s.getEventTime (1), 2.0); | |||
beginTest ("Matching note off & ons"); | |||
s.updateMatchedPairs(); | |||
expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0); | |||
expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0); | |||
expectEquals (s.getIndexOfMatchingKeyUp (0), 2); | |||
expectEquals (s.getIndexOfMatchingKeyUp (1), 3); | |||
beginTest ("Time & indices"); | |||
expectEquals (s.getNextIndexAtTime (0.5), 1); | |||
expectEquals (s.getNextIndexAtTime (2.5), 2); | |||
expectEquals (s.getNextIndexAtTime (9.0), 4); | |||
beginTest ("Deleting events"); | |||
s.deleteEvent (0, true); | |||
expectEquals (s.getNumEvents(), 2); | |||
beginTest ("Merging sequences"); | |||
MidiMessageSequence s2; | |||
s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0)); | |||
s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0)); | |||
s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0)); | |||
s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0)); | |||
s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0)); | |||
s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0)); | |||
s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off | |||
s.updateMatchedPairs(); | |||
expectEquals (s.getNumEvents(), 7); | |||
expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off | |||
expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0); | |||
struct ControlValue { int control, value; }; | |||
struct DataEntry | |||
{ | |||
int controllerBase, channel, parameter, value; | |||
double time; | |||
std::array<ControlValue, 4> getControlValues() const | |||
{ | |||
return { { { controllerBase + 1, (parameter >> 7) & 0x7f }, | |||
{ controllerBase + 0, (parameter >> 0) & 0x7f }, | |||
{ 0x06, (value >> 7) & 0x7f }, | |||
{ 0x26, (value >> 0) & 0x7f } } }; | |||
} | |||
void addToSequence (MidiMessageSequence& s) const | |||
{ | |||
for (const auto& pair : getControlValues()) | |||
s.addEvent (MidiMessage::controllerEvent (channel, pair.control, pair.value), time); | |||
} | |||
bool matches (const MidiMessage* begin, const MidiMessage* end) const | |||
{ | |||
const auto isEqual = [this] (const ControlValue& cv, const MidiMessage& msg) | |||
{ | |||
return msg.getTimeStamp() == time | |||
&& msg.isController() | |||
&& msg.getChannel() == channel | |||
&& msg.getControllerNumber() == cv.control | |||
&& msg.getControllerValue() == cv.value; | |||
}; | |||
const auto pairs = getControlValues(); | |||
return std::equal (pairs.begin(), pairs.end(), begin, end, isEqual); | |||
} | |||
}; | |||
const auto addNrpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
DataEntry { 0x62, channel, parameter, value, time }.addToSequence (seq); | |||
}; | |||
const auto addRpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
DataEntry { 0x64, channel, parameter, value, time }.addToSequence (seq); | |||
}; | |||
const auto checkNrpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
expect (DataEntry { 0x62, channel, parameter, value, time }.matches (begin, end)); | |||
}; | |||
const auto checkRpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
expect (DataEntry { 0x64, channel, parameter, value, time }.matches (begin, end)); | |||
}; | |||
beginTest ("createControllerUpdatesForTime should emit (N)RPN components in the correct order"); | |||
{ | |||
const auto channel = 1; | |||
const auto number = 200; | |||
const auto value = 300; | |||
MidiMessageSequence sequence; | |||
addNrpn (sequence, channel, number, value); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
checkNrpn (m.begin(), m.end(), channel, number, value); | |||
} | |||
beginTest ("createControllerUpdatesForTime ignores (N)RPNs after the final requested time"); | |||
{ | |||
const auto channel = 2; | |||
const auto number = 123; | |||
const auto value = 456; | |||
MidiMessageSequence sequence; | |||
addRpn (sequence, channel, number, value, 0.5); | |||
addRpn (sequence, channel, 111, 222, 1.5); | |||
addRpn (sequence, channel, 333, 444, 2.5); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
checkRpn (m.begin(), std::next (m.begin(), 4), channel, number, value, 0.5); | |||
} | |||
beginTest ("createControllerUpdatesForTime should emit separate (N)RPN messages when appropriate"); | |||
{ | |||
const auto channel = 2; | |||
const auto numberA = 1111; | |||
const auto valueA = 9999; | |||
const auto numberB = 8888; | |||
const auto valueB = 2222; | |||
const auto numberC = 7777; | |||
const auto valueC = 3333; | |||
const auto numberD = 6666; | |||
const auto valueD = 4444; | |||
const auto time = 0.5; | |||
MidiMessageSequence sequence; | |||
addRpn (sequence, channel, numberA, valueA, time); | |||
addRpn (sequence, channel, numberB, valueB, time); | |||
addNrpn (sequence, channel, numberC, valueC, time); | |||
addNrpn (sequence, channel, numberD, valueD, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, time * 2, m); | |||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), channel, numberA, valueA, time); | |||
checkRpn (std::next (m.begin(), 4), std::next (m.begin(), 8), channel, numberB, valueB, time); | |||
checkNrpn (std::next (m.begin(), 8), std::next (m.begin(), 12), channel, numberC, valueC, time); | |||
checkNrpn (std::next (m.begin(), 12), std::next (m.begin(), 16), channel, numberD, valueD, time); | |||
} | |||
beginTest ("createControllerUpdatesForTime correctly emits (N)RPN messages on multiple channels"); | |||
{ | |||
struct Info { int channel, number, value; }; | |||
const Info infos[] { { 2, 1111, 9999 }, | |||
{ 8, 8888, 2222 }, | |||
{ 5, 7777, 3333 }, | |||
{ 1, 6666, 4444 } }; | |||
const auto time = 0.5; | |||
MidiMessageSequence sequence; | |||
for (const auto& info : infos) | |||
addRpn (sequence, info.channel, info.number, info.value, time); | |||
for (const auto& info : infos) | |||
{ | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (info.channel, time * 2, m); | |||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), info.channel, info.number, info.value, time); | |||
} | |||
} | |||
const auto messagesAreEqual = [] (const MidiMessage& a, const MidiMessage& b) | |||
{ | |||
return std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(), | |||
b.getRawData(), b.getRawData() + b.getRawDataSize()); | |||
}; | |||
beginTest ("createControllerUpdatesForTime sends bank select messages when the next program is in a new bank"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
sequence.addEvent (MidiMessage::programChange (channel, 5), time); | |||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x00, 128), time); | |||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x20, 64), time); | |||
sequence.addEvent (MidiMessage::programChange (channel, 63), time); | |||
const Array<MidiMessage> finalEvents { MidiMessage::controllerEvent (channel, 0x00, 50), | |||
MidiMessage::controllerEvent (channel, 0x20, 40), | |||
MidiMessage::programChange (channel, 30) }; | |||
for (const auto& e : finalEvents) | |||
sequence.addEvent (e); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
expect (std::equal (m.begin(), m.end(), finalEvents.begin(), finalEvents.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime preserves all Data Increment and Data Decrement messages"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x60, 0), | |||
MidiMessage::controllerEvent (channel, 0x06, 100), | |||
MidiMessage::controllerEvent (channel, 0x26, 50), | |||
MidiMessage::controllerEvent (channel, 0x60, 10), | |||
MidiMessage::controllerEvent (channel, 0x61, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 20), | |||
MidiMessage::controllerEvent (channel, 0x26, 30), | |||
MidiMessage::controllerEvent (channel, 0x61, 10), | |||
MidiMessage::controllerEvent (channel, 0x61, 20) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
expect (std::equal (m.begin(), m.end(), messages.begin(), messages.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime does not emit redundant parameter number changes"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 100), | |||
MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime sets parameter number correctly at end of sequence"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 100), | |||
MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
MidiMessage::controllerEvent (channel, 0x64, 5) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
// Note: we should send both the MSB and LSB! | |||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 5).withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime does not emit duplicate parameter number change messages"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 1), | |||
MidiMessage::controllerEvent (channel, 0x64, 2), | |||
MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
MidiMessage::controllerEvent (channel, 0x63, 30), | |||
MidiMessage::controllerEvent (channel, 0x62, 40), | |||
MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x60, 5), | |||
MidiMessage::controllerEvent (channel, 0x65, 10) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
// Parameter number is set to (30, 40) then back to (3, 4), | |||
// so there is no need to resend it | |||
MidiMessage::controllerEvent (channel, 0x60, 5), | |||
// Set parameter number to final value | |||
MidiMessage::controllerEvent (channel, 0x65, 10).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 2) .withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime emits bank change messages immediately before program change"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x00, 1), | |||
MidiMessage::controllerEvent (channel, 0x20, 2), | |||
MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 0), | |||
MidiMessage::programChange (channel, 5) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x00, 1), | |||
MidiMessage::controllerEvent (channel, 0x20, 2), | |||
MidiMessage::programChange (channel, 5), | |||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 0).withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
} | |||
}; | |||
static MidiMessageSequenceTest midiMessageSequenceTests; | |||
#endif | |||
} // namespace juce |
@@ -1,315 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
A sequence of timestamped midi messages. | |||
This allows the sequence to be manipulated, and also to be read from and | |||
written to a standard midi file. | |||
@see MidiMessage, MidiFile | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiMessageSequence | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty midi sequence object. */ | |||
MidiMessageSequence(); | |||
/** Creates a copy of another sequence. */ | |||
MidiMessageSequence (const MidiMessageSequence&); | |||
/** Replaces this sequence with another one. */ | |||
MidiMessageSequence& operator= (const MidiMessageSequence&); | |||
/** Move constructor */ | |||
MidiMessageSequence (MidiMessageSequence&&) noexcept; | |||
/** Move assignment operator */ | |||
MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; | |||
//============================================================================== | |||
/** Structure used to hold midi events in the sequence. | |||
These structures act as 'handles' on the events as they are moved about in | |||
the list, and make it quick to find the matching note-offs for note-on events. | |||
@see MidiMessageSequence::getEventPointer | |||
*/ | |||
class MidiEventHolder | |||
{ | |||
public: | |||
//============================================================================== | |||
/** The message itself, whose timestamp is used to specify the event's time. */ | |||
MidiMessage message; | |||
/** The matching note-off event (if this is a note-on event). | |||
If this isn't a note-on, this pointer will be nullptr. | |||
Use the MidiMessageSequence::updateMatchedPairs() method to keep these | |||
note-offs up-to-date after events have been moved around in the sequence | |||
or deleted. | |||
*/ | |||
MidiEventHolder* noteOffObject = nullptr; | |||
private: | |||
//============================================================================== | |||
friend class MidiMessageSequence; | |||
MidiEventHolder (const MidiMessage&); | |||
MidiEventHolder (MidiMessage&&); | |||
JUCE_LEAK_DETECTOR (MidiEventHolder) | |||
}; | |||
//============================================================================== | |||
/** Clears the sequence. */ | |||
void clear(); | |||
/** Returns the number of events in the sequence. */ | |||
int getNumEvents() const noexcept; | |||
/** Returns a pointer to one of the events. */ | |||
MidiEventHolder* getEventPointer (int index) const noexcept; | |||
/** Iterator for the list of MidiEventHolders */ | |||
MidiEventHolder** begin() noexcept; | |||
/** Iterator for the list of MidiEventHolders */ | |||
MidiEventHolder* const* begin() const noexcept; | |||
/** Iterator for the list of MidiEventHolders */ | |||
MidiEventHolder** end() noexcept; | |||
/** Iterator for the list of MidiEventHolders */ | |||
MidiEventHolder* const* end() const noexcept; | |||
/** Returns the time of the note-up that matches the note-on at this index. | |||
If the event at this index isn't a note-on, it'll just return 0. | |||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||
*/ | |||
double getTimeOfMatchingKeyUp (int index) const noexcept; | |||
/** Returns the index of the note-up that matches the note-on at this index. | |||
If the event at this index isn't a note-on, it'll just return -1. | |||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||
*/ | |||
int getIndexOfMatchingKeyUp (int index) const noexcept; | |||
/** Returns the index of an event. */ | |||
int getIndexOf (const MidiEventHolder* event) const noexcept; | |||
/** Returns the index of the first event on or after the given timestamp. | |||
If the time is beyond the end of the sequence, this will return the | |||
number of events. | |||
*/ | |||
int getNextIndexAtTime (double timeStamp) const noexcept; | |||
//============================================================================== | |||
/** Returns the timestamp of the first event in the sequence. | |||
@see getEndTime | |||
*/ | |||
double getStartTime() const noexcept; | |||
/** Returns the timestamp of the last event in the sequence. | |||
@see getStartTime | |||
*/ | |||
double getEndTime() const noexcept; | |||
/** Returns the timestamp of the event at a given index. | |||
If the index is out-of-range, this will return 0.0 | |||
*/ | |||
double getEventTime (int index) const noexcept; | |||
//============================================================================== | |||
/** Inserts a midi message into the sequence. | |||
The index at which the new message gets inserted will depend on its timestamp, | |||
because the sequence is kept sorted. | |||
Remember to call updateMatchedPairs() after adding note-on events. | |||
@param newMessage the new message to add (an internal copy will be made) | |||
@param timeAdjustment an optional value to add to the timestamp of the message | |||
that will be inserted | |||
@see updateMatchedPairs | |||
*/ | |||
MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); | |||
/** Inserts a midi message into the sequence. | |||
The index at which the new message gets inserted will depend on its timestamp, | |||
because the sequence is kept sorted. | |||
Remember to call updateMatchedPairs() after adding note-on events. | |||
@param newMessage the new message to add (an internal copy will be made) | |||
@param timeAdjustment an optional value to add to the timestamp of the message | |||
that will be inserted | |||
@see updateMatchedPairs | |||
*/ | |||
MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0); | |||
/** Deletes one of the events in the sequence. | |||
Remember to call updateMatchedPairs() after removing events. | |||
@param index the index of the event to delete | |||
@param deleteMatchingNoteUp whether to also remove the matching note-off | |||
if the event you're removing is a note-on | |||
*/ | |||
void deleteEvent (int index, bool deleteMatchingNoteUp); | |||
/** Merges another sequence into this one. | |||
Remember to call updateMatchedPairs() after using this method. | |||
@param other the sequence to add from | |||
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events | |||
as they are read from the other sequence | |||
@param firstAllowableDestTime events will not be added if their time is earlier | |||
than this time. (This is after their time has been adjusted | |||
by the timeAdjustmentDelta) | |||
@param endOfAllowableDestTimes events will not be added if their time is equal to | |||
or greater than this time. (This is after their time has | |||
been adjusted by the timeAdjustmentDelta) | |||
*/ | |||
void addSequence (const MidiMessageSequence& other, | |||
double timeAdjustmentDelta, | |||
double firstAllowableDestTime, | |||
double endOfAllowableDestTimes); | |||
/** Merges another sequence into this one. | |||
Remember to call updateMatchedPairs() after using this method. | |||
@param other the sequence to add from | |||
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events | |||
as they are read from the other sequence | |||
*/ | |||
void addSequence (const MidiMessageSequence& other, | |||
double timeAdjustmentDelta); | |||
//============================================================================== | |||
/** Makes sure all the note-on and note-off pairs are up-to-date. | |||
Call this after re-ordering messages or deleting/adding messages, and it | |||
will scan the list and make sure all the note-offs in the MidiEventHolder | |||
structures are pointing at the correct ones. | |||
*/ | |||
void updateMatchedPairs() noexcept; | |||
/** Forces a sort of the sequence. | |||
You may need to call this if you've manually modified the timestamps of some | |||
events such that the overall order now needs updating. | |||
*/ | |||
void sort() noexcept; | |||
//============================================================================== | |||
/** Copies all the messages for a particular midi channel to another sequence. | |||
@param channelNumberToExtract the midi channel to look for, in the range 1 to 16 | |||
@param destSequence the sequence that the chosen events should be copied to | |||
@param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific | |||
channel) will also be copied across. | |||
@see extractSysExMessages | |||
*/ | |||
void extractMidiChannelMessages (int channelNumberToExtract, | |||
MidiMessageSequence& destSequence, | |||
bool alsoIncludeMetaEvents) const; | |||
/** Copies all midi sys-ex messages to another sequence. | |||
@param destSequence this is the sequence to which any sys-exes in this sequence | |||
will be added | |||
@see extractMidiChannelMessages | |||
*/ | |||
void extractSysExMessages (MidiMessageSequence& destSequence) const; | |||
/** Removes any messages in this sequence that have a specific midi channel. | |||
@param channelNumberToRemove the midi channel to look for, in the range 1 to 16 | |||
*/ | |||
void deleteMidiChannelMessages (int channelNumberToRemove); | |||
/** Removes any sys-ex messages from this sequence. */ | |||
void deleteSysExMessages(); | |||
/** Adds an offset to the timestamps of all events in the sequence. | |||
@param deltaTime the amount to add to each timestamp. | |||
*/ | |||
void addTimeToMessages (double deltaTime) noexcept; | |||
//============================================================================== | |||
/** Scans through the sequence to determine the state of any midi controllers at | |||
a given time. | |||
This will create a sequence of midi controller changes that can be | |||
used to set all midi controllers to the state they would be in at the | |||
specified time within this sequence. | |||
As well as controllers, it will also recreate the midi program number | |||
and pitch bend position. | |||
This function has special handling for the "bank select" and "data entry" | |||
controllers (0x00, 0x20, 0x06, 0x26, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65). | |||
If the sequence contains multiple bank select and program change messages, | |||
only the bank select messages immediately preceding the final program change | |||
message will be kept. | |||
All "data increment" and "data decrement" messages will be retained. Some hardware will | |||
ignore the requested increment/decrement values, so retaining all messages is the only | |||
way to ensure compatibility with all hardware. | |||
"Parameter number" changes will be slightly condensed. Only the parameter number | |||
events immediately preceding each data entry event will be kept. The parameter number | |||
will also be set to its final value at the end of the sequence, if necessary. | |||
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers | |||
for other channels will be ignored. | |||
@param time the time at which you want to find out the state - there are | |||
no explicit units for this time measurement, it's the same units | |||
as used for the timestamps of the messages | |||
@param resultMessages an array to which midi controller-change messages will be added. This | |||
will be the minimum number of controller changes to recreate the | |||
state at the required time. | |||
*/ | |||
void createControllerUpdatesForTime (int channelNumber, double time, | |||
Array<MidiMessage>& resultMessages); | |||
//============================================================================== | |||
/** Swaps this sequence with another one. */ | |||
void swapWith (MidiMessageSequence&) noexcept; | |||
private: | |||
//============================================================================== | |||
friend class MidiFile; | |||
OwnedArray<MidiEventHolder> list; | |||
MidiEventHolder* addEvent (MidiEventHolder*, double); | |||
JUCE_LEAK_DETECTOR (MidiMessageSequence) | |||
}; | |||
} // namespace juce |
@@ -1,375 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MidiRPNDetector::MidiRPNDetector() noexcept | |||
{ | |||
} | |||
MidiRPNDetector::~MidiRPNDetector() noexcept | |||
{ | |||
} | |||
bool MidiRPNDetector::parseControllerMessage (int midiChannel, | |||
int controllerNumber, | |||
int controllerValue, | |||
MidiRPNMessage& result) noexcept | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (controllerNumber >= 0 && controllerNumber < 128); | |||
jassert (controllerValue >= 0 && controllerValue < 128); | |||
return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); | |||
} | |||
void MidiRPNDetector::reset() noexcept | |||
{ | |||
for (int i = 0; i < 16; ++i) | |||
{ | |||
states[i].parameterMSB = 0xff; | |||
states[i].parameterLSB = 0xff; | |||
states[i].resetValue(); | |||
states[i].isNRPN = false; | |||
} | |||
} | |||
//============================================================================== | |||
bool MidiRPNDetector::ChannelState::handleController (int channel, | |||
int controllerNumber, | |||
int value, | |||
MidiRPNMessage& result) noexcept | |||
{ | |||
switch (controllerNumber) | |||
{ | |||
case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; | |||
case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; | |||
case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; | |||
case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; | |||
case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); | |||
case 0x26: valueLSB = uint8 (value); break; | |||
default: break; | |||
} | |||
return false; | |||
} | |||
void MidiRPNDetector::ChannelState::resetValue() noexcept | |||
{ | |||
valueMSB = 0xff; | |||
valueLSB = 0xff; | |||
} | |||
//============================================================================== | |||
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept | |||
{ | |||
if (parameterMSB < 0x80 && parameterLSB < 0x80) | |||
{ | |||
if (valueMSB < 0x80) | |||
{ | |||
result.channel = channel; | |||
result.parameterNumber = (parameterMSB << 7) + parameterLSB; | |||
result.isNRPN = isNRPN; | |||
if (valueLSB < 0x80) | |||
{ | |||
result.value = (valueMSB << 7) + valueLSB; | |||
result.is14BitValue = true; | |||
} | |||
else | |||
{ | |||
result.value = valueMSB; | |||
result.is14BitValue = false; | |||
} | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) | |||
{ | |||
return generate (message.channel, | |||
message.parameterNumber, | |||
message.value, | |||
message.isNRPN, | |||
message.is14BitValue); | |||
} | |||
MidiBuffer MidiRPNGenerator::generate (int midiChannel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN, | |||
bool use14BitValue) | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (parameterNumber >= 0 && parameterNumber < 16384); | |||
jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); | |||
uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); | |||
uint8 parameterMSB = uint8 (parameterNumber >> 7); | |||
uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; | |||
uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); | |||
uint8 channelByte = uint8 (0xb0 + midiChannel - 1); | |||
MidiBuffer buffer; | |||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); | |||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); | |||
// sending the value LSB is optional, but must come before sending the value MSB: | |||
if (use14BitValue) | |||
buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); | |||
buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); | |||
return buffer; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MidiRPNDetectorTests : public UnitTest | |||
{ | |||
public: | |||
MidiRPNDetectorTests() | |||
: UnitTest ("MidiRPNDetector class", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("7-bit RPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expectEquals (rpn.channel, 2); | |||
expectEquals (rpn.parameterNumber, 7); | |||
expectEquals (rpn.value, 42); | |||
expect (! rpn.isNRPN); | |||
expect (! rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit RPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("RPNs on multiple channels simultaneously"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expectEquals (rpn.channel, 2); | |||
expectEquals (rpn.parameterNumber, 7); | |||
expectEquals (rpn.value, 42); | |||
expect (! rpn.isNRPN); | |||
expect (! rpn.is14BitValue); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit RPN with value within 7-bit range"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||
expectEquals (rpn.channel, 16); | |||
expectEquals (rpn.parameterNumber, 0); | |||
expectEquals (rpn.value, 3); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("invalid RPN (wrong order)"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
} | |||
beginTest ("14-bit RPN interspersed with unrelated CC messages"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (16, 3, 80, rpn)); | |||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||
expect (! detector.parseControllerMessage (16, 4, 81, rpn)); | |||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (16, 5, 82, rpn)); | |||
expect (! detector.parseControllerMessage (16, 5, 83, rpn)); | |||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||
expect (! detector.parseControllerMessage (16, 4, 84, rpn)); | |||
expect (! detector.parseControllerMessage (16, 3, 85, rpn)); | |||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||
expectEquals (rpn.channel, 16); | |||
expectEquals (rpn.parameterNumber, 0); | |||
expectEquals (rpn.value, 3); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit NRPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 98, 44, rpn)); | |||
expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("reset"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
detector.reset(); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||
} | |||
} | |||
}; | |||
static MidiRPNDetectorTests MidiRPNDetectorUnitTests; | |||
//============================================================================== | |||
class MidiRPNGeneratorTests : public UnitTest | |||
{ | |||
public: | |||
MidiRPNGeneratorTests() | |||
: UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("generating RPN/NRPN"); | |||
{ | |||
{ | |||
MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); | |||
expectContainsRPN (buffer, 1, 23, 1337, true, true); | |||
} | |||
{ | |||
MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); | |||
expectContainsRPN (buffer, 16, 101, 34, false, false); | |||
} | |||
{ | |||
MidiRPNMessage message = { 16, 101, 34, false, false }; | |||
MidiBuffer buffer = MidiRPNGenerator::generate (message); | |||
expectContainsRPN (buffer, message); | |||
} | |||
} | |||
} | |||
private: | |||
//============================================================================== | |||
void expectContainsRPN (const MidiBuffer& midiBuffer, | |||
int channel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN, | |||
bool is14BitValue) | |||
{ | |||
MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; | |||
expectContainsRPN (midiBuffer, expected); | |||
} | |||
//============================================================================== | |||
void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | |||
{ | |||
MidiRPNMessage result = MidiRPNMessage(); | |||
MidiRPNDetector detector; | |||
for (const auto metadata : midiBuffer) | |||
{ | |||
const auto midiMessage = metadata.getMessage(); | |||
if (detector.parseControllerMessage (midiMessage.getChannel(), | |||
midiMessage.getControllerNumber(), | |||
midiMessage.getControllerValue(), | |||
result)) | |||
break; | |||
} | |||
expectEquals (result.channel, expected.channel); | |||
expectEquals (result.parameterNumber, expected.parameterNumber); | |||
expectEquals (result.value, expected.value); | |||
expect (result.isNRPN == expected.isNRPN); | |||
expect (result.is14BitValue == expected.is14BitValue); | |||
} | |||
}; | |||
static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; | |||
#endif | |||
} // namespace juce |
@@ -1,153 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | |||
parameter number) message. | |||
@tags{Audio} | |||
*/ | |||
struct MidiRPNMessage | |||
{ | |||
/** Midi channel of the message, in the range 1 to 16. */ | |||
int channel; | |||
/** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ | |||
int parameterNumber; | |||
/** The parameter value, in the range 0 to 16383 (0x3fff). | |||
If the message contains no value LSB, the value will be in the range | |||
0 to 127 (0x7f). | |||
*/ | |||
int value; | |||
/** True if this message is an NRPN; false if it is an RPN. */ | |||
bool isNRPN; | |||
/** True if the value uses 14-bit resolution (LSB + MSB); false if | |||
the value is 7-bit (MSB only). | |||
*/ | |||
bool is14BitValue; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of MIDI data to assemble RPN and NRPN messages from their | |||
constituent MIDI CC messages. | |||
The detector uses the following parsing rules: the parameter number | |||
LSB/MSB can be sent/received in either order and must both come before the | |||
parameter value; for the parameter value, LSB always has to be sent/received | |||
before the value MSB, otherwise it will be treated as 7-bit (MSB only). | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiRPNDetector | |||
{ | |||
public: | |||
/** Constructor. */ | |||
MidiRPNDetector() noexcept; | |||
/** Destructor. */ | |||
~MidiRPNDetector() noexcept; | |||
/** Resets the RPN detector's internal state, so that it forgets about | |||
previously received MIDI CC messages. | |||
*/ | |||
void reset() noexcept; | |||
//============================================================================== | |||
/** Takes the next in a stream of incoming MIDI CC messages and returns true | |||
if it forms the last of a sequence that makes an RPN or NPRN. | |||
If this returns true, then the RPNMessage object supplied will be | |||
filled-out with the message's details. | |||
(If it returns false then the RPNMessage object will be unchanged). | |||
*/ | |||
bool parseControllerMessage (int midiChannel, | |||
int controllerNumber, | |||
int controllerValue, | |||
MidiRPNMessage& result) noexcept; | |||
private: | |||
//============================================================================== | |||
struct ChannelState | |||
{ | |||
bool handleController (int channel, int controllerNumber, | |||
int value, MidiRPNMessage&) noexcept; | |||
void resetValue() noexcept; | |||
bool sendIfReady (int channel, MidiRPNMessage&) noexcept; | |||
uint8 parameterMSB = 0xff, parameterLSB = 0xff, valueMSB = 0xff, valueLSB = 0xff; | |||
bool isNRPN = false; | |||
}; | |||
//============================================================================== | |||
ChannelState states[16]; | |||
JUCE_LEAK_DETECTOR (MidiRPNDetector) | |||
}; | |||
//============================================================================== | |||
/** | |||
Generates an appropriate sequence of MIDI CC messages to represent an RPN | |||
or NRPN message. | |||
This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MidiRPNGenerator | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Generates a MIDI sequence representing the given RPN or NRPN message. */ | |||
static MidiBuffer generate (MidiRPNMessage message); | |||
//============================================================================== | |||
/** Generates a MIDI sequence representing an RPN or NRPN message with the | |||
given parameters. | |||
@param channel The MIDI channel of the RPN/NRPN message. | |||
@param parameterNumber The parameter number, in the range 0 to 16383. | |||
@param value The parameter value, in the range 0 to 16383, or | |||
in the range 0 to 127 if sendAs14BitValue is false. | |||
@param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). | |||
@param use14BitValue If true (default), the value will have 14-bit precision | |||
(two MIDI bytes). If false, instead the value will have | |||
7-bit precision (a single MIDI byte). | |||
*/ | |||
static MidiBuffer generate (int channel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN = false, | |||
bool use14BitValue = true); | |||
}; | |||
} // namespace juce |
@@ -1,47 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#include "../juce_MidiDataConcatenator.h" | |||
#include "juce_UMPProtocols.h" | |||
#include "juce_UMPUtils.h" | |||
#include "juce_UMPacket.h" | |||
#include "juce_UMPSysEx7.h" | |||
#include "juce_UMPView.h" | |||
#include "juce_UMPIterator.h" | |||
#include "juce_UMPackets.h" | |||
#include "juce_UMPFactory.h" | |||
#include "juce_UMPConversion.h" | |||
#include "juce_UMPMidi1ToBytestreamTranslator.h" | |||
#include "juce_UMPMidi1ToMidi2DefaultTranslator.h" | |||
#include "juce_UMPConverters.h" | |||
#include "juce_UMPDispatcher.h" | |||
#include "juce_UMPReceiver.h" | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace ump = universal_midi_packets; | |||
} | |||
#endif |
@@ -1,330 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Functions to assist conversion of UMP messages to/from other formats, | |||
especially older 'bytestream' formatted MidiMessages. | |||
@tags{Audio} | |||
*/ | |||
struct Conversion | |||
{ | |||
/** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets. | |||
`callback` is a function which accepts a single View argument. | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
static void toMidi1 (const MidiMessage& m, PacketCallbackFunction&& callback) | |||
{ | |||
const auto* data = m.getRawData(); | |||
const auto firstByte = data[0]; | |||
const auto size = m.getRawDataSize(); | |||
if (firstByte != 0xf0) | |||
{ | |||
const auto mask = [size]() -> uint32_t | |||
{ | |||
switch (size) | |||
{ | |||
case 0: return 0xff000000; | |||
case 1: return 0xffff0000; | |||
case 2: return 0xffffff00; | |||
case 3: return 0xffffffff; | |||
} | |||
return 0x00000000; | |||
}(); | |||
const auto extraByte = (uint8_t) ((((firstByte & 0xf0) == 0xf0) ? 0x1 : 0x2) << 0x4); | |||
const PacketX1 packet { mask & Utils::bytesToWord (extraByte, data[0], data[1], data[2]) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
const auto numSysExBytes = m.getSysExDataSize(); | |||
const auto numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes); | |||
auto* dataOffset = m.getSysExData(); | |||
if (numMessages <= 1) | |||
{ | |||
const auto packet = Factory::makeSysExIn1Packet (0, (uint8_t) numSysExBytes, dataOffset); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
constexpr auto byteIncrement = 6; | |||
for (auto i = numSysExBytes; i > 0; i -= byteIncrement, dataOffset += byteIncrement) | |||
{ | |||
const auto func = [&] | |||
{ | |||
if (i == numSysExBytes) | |||
return Factory::makeSysExStart; | |||
if (i <= byteIncrement) | |||
return Factory::makeSysExEnd; | |||
return Factory::makeSysExContinue; | |||
}(); | |||
const auto bytesNow = std::min (byteIncrement, i); | |||
const auto packet = func (0, (uint8_t) bytesNow, dataOffset); | |||
callback (View (packet.data())); | |||
} | |||
} | |||
/** Converts a MidiMessage to one or more messages in UMP format, using | |||
the MIDI 1.0 Protocol. | |||
`packets` is an out-param to allow the caller to control | |||
allocation/deallocation. Returning a new Packets object would | |||
require every call to toMidi1 to allocate. With this version, no | |||
allocations will occur, provided that `packets` has adequate reserved | |||
space. | |||
*/ | |||
static void toMidi1 (const MidiMessage& m, Packets& packets) | |||
{ | |||
toMidi1 (m, [&] (const View& view) { packets.add (view); }); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 8-bit MIDI 2.0 value. */ | |||
static uint8_t scaleTo8 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint8_t) (word7Bit << 0x1); | |||
const auto repeat = (uint8_t) (word7Bit & 0x3f); | |||
const auto mask = (uint8_t) (word7Bit <= 0x40 ? 0x0 : 0xff); | |||
return (uint8_t) (shifted | ((repeat >> 5) & mask)); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | |||
static uint16_t scaleTo16 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint16_t) (word7Bit << 0x9); | |||
const auto repeat = (uint16_t) (word7Bit & 0x3f); | |||
const auto mask = (uint16_t) (word7Bit <= 0x40 ? 0x0 : 0xffff); | |||
return (uint16_t) (shifted | (((repeat << 3) | (repeat >> 3)) & mask)); | |||
} | |||
/** Widens a 14-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | |||
static uint16_t scaleTo16 (uint16_t word14Bit) | |||
{ | |||
const auto shifted = (uint16_t) (word14Bit << 0x2); | |||
const auto repeat = (uint16_t) (word14Bit & 0x1fff); | |||
const auto mask = (uint16_t) (word14Bit <= 0x2000 ? 0x0 : 0xffff); | |||
return (uint16_t) (shifted | ((repeat >> 11) & mask)); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | |||
static uint32_t scaleTo32 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint32_t) (word7Bit << 0x19); | |||
const auto repeat = (uint32_t) (word7Bit & 0x3f); | |||
const auto mask = (uint32_t) (word7Bit <= 0x40 ? 0x0 : 0xffffffff); | |||
return (uint32_t) (shifted | (((repeat << 19) | |||
| (repeat << 13) | |||
| (repeat << 7) | |||
| (repeat << 1) | |||
| (repeat >> 5)) & mask)); | |||
} | |||
/** Widens a 14-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | |||
static uint32_t scaleTo32 (uint16_t word14Bit) | |||
{ | |||
const auto shifted = (uint32_t) (word14Bit << 0x12); | |||
const auto repeat = (uint32_t) (word14Bit & 0x1fff); | |||
const auto mask = (uint32_t) (word14Bit <= 0x2000 ? 0x0 : 0xffffffff); | |||
return (uint32_t) (shifted | (((repeat << 5) | (repeat >> 8)) & mask)); | |||
} | |||
/** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint8_t word8Bit) { return (uint8_t) (word8Bit >> 1); } | |||
/** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint16_t word16Bit) { return (uint8_t) (word16Bit >> 9); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint32_t word32Bit) { return (uint8_t) (word32Bit >> 25); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | |||
static uint16_t scaleTo14 (uint16_t word16Bit) { return (uint16_t) (word16Bit >> 2); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | |||
static uint16_t scaleTo14 (uint32_t word32Bit) { return (uint16_t) (word32Bit >> 18); } | |||
/** Converts UMP messages which may include MIDI 2.0 channel voice messages into | |||
equivalent MIDI 1.0 messages (still in UMP format). | |||
`callback` is a function that accepts a single View argument and will be | |||
called with each converted packet. | |||
Note that not all MIDI 2.0 messages have MIDI 1.0 equivalents, so such | |||
messages will be ignored. | |||
*/ | |||
template <typename Callback> | |||
static void midi2ToMidi1DefaultTranslation (const View& v, Callback&& callback) | |||
{ | |||
const auto firstWord = v[0]; | |||
if (Utils::getMessageType (firstWord) != 0x4) | |||
{ | |||
callback (v); | |||
return; | |||
} | |||
const auto status = Utils::getStatus (firstWord); | |||
const auto typeAndGroup = (uint8_t) ((0x2 << 0x4) | Utils::getGroup (firstWord)); | |||
switch (status) | |||
{ | |||
case 0x8: // note off | |||
case 0x9: // note on | |||
case 0xa: // poly pressure | |||
case 0xb: // control change | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const auto byte2 = (uint8_t) ((firstWord >> 0x08) & 0xff); | |||
const auto byte3 = scaleTo7 (v[1]); | |||
// If this is a note-on, and the scaled byte is 0, | |||
// the scaled velocity should be 1 instead of 0 | |||
const auto needsCorrection = status == 0x9 && byte3 == 0; | |||
const auto correctedByte = (uint8_t) (needsCorrection ? 1 : byte3); | |||
const auto shouldIgnore = status == 0xb && [&] | |||
{ | |||
switch (byte2) | |||
{ | |||
case 0: | |||
case 6: | |||
case 32: | |||
case 38: | |||
case 98: | |||
case 99: | |||
case 100: | |||
case 101: | |||
return true; | |||
} | |||
return false; | |||
}(); | |||
if (shouldIgnore) | |||
return; | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
byte2, | |||
correctedByte) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xd: // channel pressure | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const auto byte2 = scaleTo7 (v[1]); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
byte2, | |||
0) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0x2: // rpn | |||
case 0x3: // nrpn | |||
{ | |||
const auto ccX = (uint8_t) (status == 0x2 ? 101 : 99); | |||
const auto ccY = (uint8_t) (status == 0x2 ? 100 : 98); | |||
const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | |||
const auto data = scaleTo14 (v[1]); | |||
const PacketX1 packets[] | |||
{ | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccX, (uint8_t) ((firstWord >> 0x8) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccY, (uint8_t) ((firstWord >> 0x0) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 6, (uint8_t) ((data >> 0x7) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 38, (uint8_t) ((data >> 0x0) & 0x7f)) }, | |||
}; | |||
for (const auto& packet : packets) | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xc: // program change / bank select | |||
{ | |||
if (firstWord & 1) | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | |||
const auto secondWord = v[1]; | |||
const PacketX1 packets[] | |||
{ | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 0, (uint8_t) ((secondWord >> 0x8) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 32, (uint8_t) ((secondWord >> 0x0) & 0x7f)) }, | |||
}; | |||
for (const auto& packet : packets) | |||
callback (View (packet.data())); | |||
} | |||
const auto statusAndChannel = (uint8_t) ((0xc << 0x4) | Utils::getChannel (firstWord)); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
(uint8_t) ((v[1] >> 0x18) & 0x7f), | |||
0) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xe: // pitch bend | |||
{ | |||
const auto data = scaleTo14 (v[1]); | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
(uint8_t) (data & 0x7f), | |||
(uint8_t) ((data >> 7) & 0x7f)) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
default: // other message types do not translate | |||
return; | |||
} | |||
} | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,169 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to MIDI 1.0 messages in UMP format. | |||
@tags{Audio} | |||
*/ | |||
struct ToUMP1Converter | |||
{ | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
Conversion::toMidi1 (m, std::forward<Fn> (fn)); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
Conversion::midi2ToMidi1DefaultTranslation (v, std::forward<Fn> (fn)); | |||
} | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to MIDI 2.0 messages in UMP format. | |||
@tags{Audio} | |||
*/ | |||
struct ToUMP2Converter | |||
{ | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
Conversion::toMidi1 (m, [&] (const View& v) | |||
{ | |||
translator.dispatch (v, fn); | |||
}); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
translator.dispatch (v, std::forward<Fn> (fn)); | |||
} | |||
void reset() | |||
{ | |||
translator.reset(); | |||
} | |||
Midi1ToMidi2DefaultTranslator translator; | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to UMP format. | |||
The packet protocol can be selected using the constructor parameter. | |||
@tags{Audio} | |||
*/ | |||
class GenericUMPConverter | |||
{ | |||
public: | |||
explicit GenericUMPConverter (PacketProtocol m) | |||
: mode (m) {} | |||
void reset() | |||
{ | |||
std::get<1> (converters).reset(); | |||
} | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
switch (mode) | |||
{ | |||
case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (m, std::forward<Fn> (fn)); | |||
case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (m, std::forward<Fn> (fn)); | |||
} | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
switch (mode) | |||
{ | |||
case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (v, std::forward<Fn> (fn)); | |||
case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (v, std::forward<Fn> (fn)); | |||
} | |||
} | |||
template <typename Fn> | |||
void convert (Iterator begin, Iterator end, Fn&& fn) | |||
{ | |||
std::for_each (begin, end, [&] (const View& v) | |||
{ | |||
convert (v, fn); | |||
}); | |||
} | |||
PacketProtocol getProtocol() const noexcept { return mode; } | |||
private: | |||
std::tuple<ToUMP1Converter, ToUMP2Converter> converters; | |||
const PacketProtocol mode{}; | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to bytestream format. | |||
@tags{Audio} | |||
*/ | |||
struct ToBytestreamConverter | |||
{ | |||
explicit ToBytestreamConverter (int storageSize) | |||
: translator (storageSize) {} | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
fn (m); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, double time, Fn&& fn) | |||
{ | |||
Conversion::midi2ToMidi1DefaultTranslation (v, [&] (const View& midi1) | |||
{ | |||
translator.dispatch (midi1, time, fn); | |||
}); | |||
} | |||
void reset() { translator.reset(); } | |||
Midi1ToBytestreamTranslator translator; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,202 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Parses a raw stream of uint32_t, and calls a user-provided callback every time | |||
a full Universal MIDI Packet is encountered. | |||
@tags{Audio} | |||
*/ | |||
class Dispatcher | |||
{ | |||
public: | |||
/** Clears the dispatcher. */ | |||
void reset() { currentPacketLen = 0; } | |||
/** Calls `callback` with a View of each packet encountered in the range delimited | |||
by `begin` and `end`. | |||
If the range ends part-way through a packet, the next call to `dispatch` will | |||
continue from that point in the packet (unless `reset` is called first). | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
void dispatch (const uint32_t* begin, | |||
const uint32_t* end, | |||
double timeStamp, | |||
PacketCallbackFunction&& callback) | |||
{ | |||
std::for_each (begin, end, [&] (uint32_t word) | |||
{ | |||
nextPacket[currentPacketLen++] = word; | |||
if (currentPacketLen == Utils::getNumWordsForMessageType (nextPacket.front())) | |||
{ | |||
callback (View (nextPacket.data()), timeStamp); | |||
currentPacketLen = 0; | |||
} | |||
}); | |||
} | |||
private: | |||
std::array<uint32_t, 4> nextPacket; | |||
size_t currentPacketLen = 0; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of bytes representing a sequence of bytestream-encoded MIDI 1.0 messages, | |||
converting the messages to UMP format and passing the packets to a user-provided callback | |||
as they become ready. | |||
@tags{Audio} | |||
*/ | |||
class BytestreamToUMPDispatcher | |||
{ | |||
public: | |||
/** Initialises the dispatcher. | |||
Channel messages will be converted to the requested protocol format `pp`. | |||
`storageSize` bytes will be allocated to store incomplete messages. | |||
*/ | |||
explicit BytestreamToUMPDispatcher (PacketProtocol pp, int storageSize) | |||
: concatenator (storageSize), | |||
converter (pp) | |||
{} | |||
void reset() | |||
{ | |||
concatenator.reset(); | |||
converter.reset(); | |||
} | |||
/** Calls `callback` with a View of each converted packet as it becomes ready. | |||
@param begin the first byte in a range of bytes representing bytestream-encoded MIDI messages. | |||
@param end one-past the last byte in a range of bytes representing bytestream-encoded MIDI messages. | |||
@param timestamp a timestamp to apply to the created packets. | |||
@param callback a callback which will be passed a View pointing to each new packet as it becomes ready. | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
void dispatch (const uint8_t* begin, | |||
const uint8_t* end, | |||
double timestamp, | |||
PacketCallbackFunction&& callback) | |||
{ | |||
using CallbackPtr = decltype (std::addressof (callback)); | |||
#if JUCE_MINGW | |||
#define JUCE_MINGW_HIDDEN_VISIBILITY __attribute__ ((visibility ("hidden"))) | |||
#else | |||
#define JUCE_MINGW_HIDDEN_VISIBILITY | |||
#endif | |||
struct JUCE_MINGW_HIDDEN_VISIBILITY Callback | |||
{ | |||
Callback (BytestreamToUMPDispatcher& d, CallbackPtr c) | |||
: dispatch (d), callbackPtr (c) {} | |||
void handleIncomingMidiMessage (void*, const MidiMessage& msg) const | |||
{ | |||
Conversion::toMidi1 (msg, [&] (const View& view) | |||
{ | |||
dispatch.converter.convert (view, *callbackPtr); | |||
}); | |||
} | |||
void handlePartialSysexMessage (void*, const uint8_t*, int, double) const {} | |||
BytestreamToUMPDispatcher& dispatch; | |||
CallbackPtr callbackPtr = nullptr; | |||
}; | |||
#undef JUCE_MINGW_HIDDEN_VISIBILITY | |||
Callback inputCallback { *this, &callback }; | |||
concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback); | |||
} | |||
private: | |||
MidiDataConcatenator concatenator; | |||
GenericUMPConverter converter; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of 32-bit words representing a sequence of UMP-encoded MIDI messages, | |||
converting the messages to MIDI 1.0 bytestream format and passing them to a user-provided | |||
callback as they become ready. | |||
@tags{Audio} | |||
*/ | |||
class ToBytestreamDispatcher | |||
{ | |||
public: | |||
/** Initialises the dispatcher. | |||
`storageSize` bytes will be allocated to store incomplete messages. | |||
*/ | |||
explicit ToBytestreamDispatcher (int storageSize) | |||
: converter (storageSize) {} | |||
/** Clears the dispatcher. */ | |||
void reset() | |||
{ | |||
dispatcher.reset(); | |||
converter.reset(); | |||
} | |||
/** Calls `callback` with converted bytestream-formatted MidiMessage whenever | |||
a new message becomes available. | |||
@param begin the first word in a stream of words representing UMP-encoded MIDI packets. | |||
@param end one-past the last word in a stream of words representing UMP-encoded MIDI packets. | |||
@param timestamp a timestamp to apply to converted messages. | |||
@param callback a callback which will be passed a MidiMessage each time a new message becomes ready. | |||
*/ | |||
template <typename BytestreamMessageCallback> | |||
void dispatch (const uint32_t* begin, | |||
const uint32_t* end, | |||
double timestamp, | |||
BytestreamMessageCallback&& callback) | |||
{ | |||
dispatcher.dispatch (begin, end, timestamp, [&] (const View& view, double time) | |||
{ | |||
converter.convert (view, time, callback); | |||
}); | |||
} | |||
private: | |||
Dispatcher dispatcher; | |||
ToBytestreamConverter converter; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,538 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
This struct holds functions that can be used to create different kinds | |||
of Universal MIDI Packet. | |||
@tags{Audio} | |||
*/ | |||
struct Factory | |||
{ | |||
/** @internal */ | |||
struct Detail | |||
{ | |||
static PacketX1 makeSystem() { return PacketX1{}.withMessageType (1); } | |||
static PacketX1 makeV1() { return PacketX1{}.withMessageType (2); } | |||
static PacketX2 makeV2() { return PacketX2{}.withMessageType (4); } | |||
static PacketX2 makeSysEx (uint8_t group, | |||
uint8_t status, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
jassert (numBytes <= 6); | |||
std::array<uint8_t, 8> bytes{{}}; | |||
bytes[0] = (0x3 << 0x4) | group; | |||
bytes[1] = (uint8_t) (status << 0x4) | numBytes; | |||
std::memcpy (bytes.data() + 2, data, numBytes); | |||
std::array<uint32_t, 2> words; | |||
size_t index = 0; | |||
for (auto& word : words) | |||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); | |||
return PacketX2 { words }; | |||
} | |||
static PacketX4 makeSysEx8 (uint8_t group, | |||
uint8_t status, | |||
uint8_t numBytes, | |||
uint8_t dataStart, | |||
const uint8_t* data) | |||
{ | |||
jassert (numBytes <= 16 - dataStart); | |||
std::array<uint8_t, 16> bytes{{}}; | |||
bytes[0] = (0x5 << 0x4) | group; | |||
bytes[1] = (uint8_t) (status << 0x4) | numBytes; | |||
std::memcpy (bytes.data() + dataStart, data, numBytes); | |||
std::array<uint32_t, 4> words; | |||
size_t index = 0; | |||
for (auto& word : words) | |||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); | |||
return PacketX4 { words }; | |||
} | |||
}; | |||
static PacketX1 makeNoop (uint8_t group) | |||
{ | |||
return PacketX1{}.withGroup (group); | |||
} | |||
static PacketX1 makeJRClock (uint8_t group, uint16_t time) | |||
{ | |||
return PacketX1 { time }.withStatus (1).withGroup (group); | |||
} | |||
static PacketX1 makeJRTimestamp (uint8_t group, uint16_t time) | |||
{ | |||
return PacketX1 { time }.withStatus (2).withGroup (group); | |||
} | |||
static PacketX1 makeTimeCode (uint8_t group, uint8_t code) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf1) | |||
.withU8<2> (code & 0x7f); | |||
} | |||
static PacketX1 makeSongPositionPointer (uint8_t group, uint16_t pos) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf2) | |||
.withU8<2> (pos & 0x7f) | |||
.withU8<3> ((pos >> 7) & 0x7f); | |||
} | |||
static PacketX1 makeSongSelect (uint8_t group, uint8_t song) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf3) | |||
.withU8<2> (song & 0x7f); | |||
} | |||
static PacketX1 makeTuneRequest (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf6); | |||
} | |||
static PacketX1 makeTimingClock (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf8); | |||
} | |||
static PacketX1 makeStart (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfa); | |||
} | |||
static PacketX1 makeContinue (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfb); | |||
} | |||
static PacketX1 makeStop (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfc); | |||
} | |||
static PacketX1 makeActiveSensing (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfe); | |||
} | |||
static PacketX1 makeReset (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xff); | |||
} | |||
static PacketX1 makeNoteOffV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t velocity) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0x8) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (velocity & 0x7f); | |||
} | |||
static PacketX1 makeNoteOnV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t velocity) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0x9) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (velocity & 0x7f); | |||
} | |||
static PacketX1 makePolyPressureV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t pressure) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xa) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (pressure & 0x7f); | |||
} | |||
static PacketX1 makeControlChangeV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t controller, | |||
uint8_t value) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xb) | |||
.withChannel (channel) | |||
.withU8<2> (controller & 0x7f) | |||
.withU8<3> (value & 0x7f); | |||
} | |||
static PacketX1 makeProgramChangeV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t program) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xc) | |||
.withChannel (channel) | |||
.withU8<2> (program & 0x7f); | |||
} | |||
static PacketX1 makeChannelPressureV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t pressure) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xd) | |||
.withChannel (channel) | |||
.withU8<2> (pressure & 0x7f); | |||
} | |||
static PacketX1 makePitchBend (uint8_t group, | |||
uint8_t channel, | |||
uint16_t pitchbend) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xe) | |||
.withChannel (channel) | |||
.withU8<2> (pitchbend & 0x7f) | |||
.withU8<3> ((pitchbend >> 7) & 0x7f); | |||
} | |||
static PacketX2 makeSysExIn1Packet (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x0, numBytes, data); | |||
} | |||
static PacketX2 makeSysExStart (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x1, numBytes, data); | |||
} | |||
static PacketX2 makeSysExContinue (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x2, numBytes, data); | |||
} | |||
static PacketX2 makeSysExEnd (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x3, numBytes, data); | |||
} | |||
static PacketX2 makeRegisteredPerNoteControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x0) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeAssignablePerNoteControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x1) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRegisteredControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x2) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeAssignableControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x3) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRelativeRegisteredControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x4) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRelativeAssignableControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x5) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePerNotePitchBendV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x6) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
enum class NoteAttributeKind : uint8_t | |||
{ | |||
none = 0x00, | |||
manufacturer = 0x01, | |||
profile = 0x02, | |||
pitch7_9 = 0x03 | |||
}; | |||
static PacketX2 makeNoteOffV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
NoteAttributeKind attribute, | |||
uint16_t velocity, | |||
uint16_t attributeValue) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x8) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> ((uint8_t) attribute) | |||
.withU16<2> (velocity) | |||
.withU16<3> (attributeValue); | |||
} | |||
static PacketX2 makeNoteOnV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
NoteAttributeKind attribute, | |||
uint16_t velocity, | |||
uint16_t attributeValue) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x9) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> ((uint8_t) attribute) | |||
.withU16<2> (velocity) | |||
.withU16<3> (attributeValue); | |||
} | |||
static PacketX2 makePolyPressureV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xa) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeControlChangeV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xb) | |||
.withChannel (channel) | |||
.withU8<2> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeProgramChangeV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t optionFlags, | |||
uint8_t program, | |||
uint8_t bankMsb, | |||
uint8_t bankLsb) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xc) | |||
.withChannel (channel) | |||
.withU8<3> (optionFlags) | |||
.withU8<4> (program) | |||
.withU8<6> (bankMsb) | |||
.withU8<7> (bankLsb); | |||
} | |||
static PacketX2 makeChannelPressureV2 (uint8_t group, | |||
uint8_t channel, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xd) | |||
.withChannel (channel) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePitchBendV2 (uint8_t group, | |||
uint8_t channel, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xe) | |||
.withChannel (channel) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePerNoteManagementV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t optionFlags) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xf) | |||
.withChannel (channel) | |||
.withU8<2> (note) | |||
.withU8<3> (optionFlags); | |||
} | |||
static PacketX4 makeSysEx8in1Packet (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x0, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8Start (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x1, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8Continue (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x2, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8End (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x3, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeMixedDataSetHeader (uint8_t group, | |||
uint8_t dataSetId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x8, 14, 2, data).withChannel (dataSetId); | |||
} | |||
static PacketX4 makeDataSetPayload (uint8_t group, | |||
uint8_t dataSetId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x9, 14, 2, data).withChannel (dataSetId); | |||
} | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,130 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Enables iteration over a collection of Universal MIDI Packets stored as | |||
a contiguous range of 32-bit words. | |||
This iterator is used by Packets to allow access to the messages | |||
that it contains. | |||
@tags{Audio} | |||
*/ | |||
class Iterator | |||
{ | |||
public: | |||
/** Creates an invalid (singular) iterator. */ | |||
Iterator() noexcept = default; | |||
/** Creates an iterator pointing at `ptr`. */ | |||
explicit Iterator (const uint32_t* ptr, size_t bytes) noexcept | |||
: view (ptr) | |||
#if JUCE_DEBUG | |||
, bytesRemaining (bytes) | |||
#endif | |||
{ | |||
ignoreUnused (bytes); | |||
} | |||
using difference_type = std::iterator_traits<const uint32_t*>::difference_type; | |||
using value_type = View; | |||
using reference = const View&; | |||
using pointer = const View*; | |||
using iterator_category = std::forward_iterator_tag; | |||
/** Moves this iterator to the next packet in the range. */ | |||
Iterator& operator++() noexcept | |||
{ | |||
const auto increment = view.size(); | |||
#if JUCE_DEBUG | |||
// If you hit this, the memory region contained a truncated or otherwise | |||
// malformed Universal MIDI Packet. | |||
// The Iterator can only be used on regions containing complete packets! | |||
jassert (increment <= bytesRemaining); | |||
bytesRemaining -= increment; | |||
#endif | |||
view = View (view.data() + increment); | |||
return *this; | |||
} | |||
/** Moves this iterator to the next packet in the range, | |||
returning the value of the iterator before it was | |||
incremented. | |||
*/ | |||
Iterator operator++ (int) noexcept | |||
{ | |||
auto copy = *this; | |||
++(*this); | |||
return copy; | |||
} | |||
/** Returns true if this iterator points to the same address | |||
as another iterator. | |||
*/ | |||
bool operator== (const Iterator& other) const noexcept | |||
{ | |||
return view == other.view; | |||
} | |||
/** Returns false if this iterator points to the same address | |||
as another iterator. | |||
*/ | |||
bool operator!= (const Iterator& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
/** Returns a reference to a View of the packet currently | |||
pointed-to by this iterator. | |||
The View can be queried for its size and content. | |||
*/ | |||
reference operator*() noexcept { return view; } | |||
/** Returns a pointer to a View of the packet currently | |||
pointed-to by this iterator. | |||
The View can be queried for its size and content. | |||
*/ | |||
pointer operator->() noexcept { return &view; } | |||
private: | |||
View view; | |||
#if JUCE_DEBUG | |||
size_t bytesRemaining = 0; | |||
#endif | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,217 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using | |||
the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages. | |||
@tags{Audio} | |||
*/ | |||
class Midi1ToBytestreamTranslator | |||
{ | |||
public: | |||
/** Ensures that there is room in the internal buffer for a sysex message of at least | |||
`initialBufferSize` bytes. | |||
*/ | |||
explicit Midi1ToBytestreamTranslator (int initialBufferSize) | |||
{ | |||
pendingSysExData.reserve (size_t (initialBufferSize)); | |||
} | |||
/** Clears the concatenator. */ | |||
void reset() | |||
{ | |||
pendingSysExData.clear(); | |||
pendingSysExTime = 0.0; | |||
} | |||
/** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to | |||
an equivalent MidiMessage. Accumulates SysEx packets into a single | |||
MidiMessage, as appropriate. | |||
@param packet a packet which is using the MIDI 1.0 Protocol. | |||
@param time the timestamp to be applied to these messages. | |||
@param callback a callback which will be called with each converted MidiMessage. | |||
*/ | |||
template <typename MessageCallback> | |||
void dispatch (const View& packet, double time, MessageCallback&& callback) | |||
{ | |||
const auto firstWord = *packet.data(); | |||
if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord)) | |||
pendingSysExData.clear(); | |||
switch (packet.size()) | |||
{ | |||
case 1: | |||
{ | |||
// Utility messages don't translate to bytestream format | |||
if (Utils::getMessageType (firstWord) != 0x00) | |||
callback (fromUmp (PacketX1 { firstWord }, time)); | |||
break; | |||
} | |||
case 2: | |||
{ | |||
if (Utils::getMessageType (firstWord) == 0x3) | |||
processSysEx (PacketX2 { packet[0], packet[1] }, time, callback); | |||
break; | |||
} | |||
case 3: // no 3-word packets in the current spec | |||
case 4: // no 4-word packets translate to bytestream format | |||
default: | |||
break; | |||
} | |||
} | |||
/** Converts from a Universal MIDI Packet to MIDI 1 bytestream format. | |||
This is only capable of converting a single Universal MIDI Packet to | |||
an equivalent bytestream MIDI message. This function cannot understand | |||
multi-packet messages, like SysEx7 messages. | |||
To convert multi-packet messages, use `Midi1ToBytestreamTranslator` | |||
to convert from a UMP MIDI 1.0 stream, or `ToBytestreamDispatcher` | |||
to convert from both MIDI 2.0 and MIDI 1.0. | |||
*/ | |||
static MidiMessage fromUmp (const PacketX1& m, double time = 0) | |||
{ | |||
const auto word = m.front(); | |||
jassert (Utils::getNumWordsForMessageType (word) == 1); | |||
const std::array<uint8_t, 3> bytes { { uint8_t ((word >> 0x10) & 0xff), | |||
uint8_t ((word >> 0x08) & 0xff), | |||
uint8_t ((word >> 0x00) & 0xff) } }; | |||
const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front()); | |||
return MidiMessage (bytes.data(), numBytes, time); | |||
} | |||
private: | |||
template <typename MessageCallback> | |||
void processSysEx (const PacketX2& packet, | |||
double time, | |||
MessageCallback&& callback) | |||
{ | |||
switch (getSysEx7Kind (packet[0])) | |||
{ | |||
case SysEx7::Kind::complete: | |||
startSysExMessage (time); | |||
pushBytes (packet); | |||
terminateSysExMessage (callback); | |||
break; | |||
case SysEx7::Kind::begin: | |||
startSysExMessage (time); | |||
pushBytes (packet); | |||
break; | |||
case SysEx7::Kind::continuation: | |||
if (pendingSysExData.empty()) | |||
break; | |||
pushBytes (packet); | |||
break; | |||
case SysEx7::Kind::end: | |||
if (pendingSysExData.empty()) | |||
break; | |||
pushBytes (packet); | |||
terminateSysExMessage (callback); | |||
break; | |||
} | |||
} | |||
void pushBytes (const PacketX2& packet) | |||
{ | |||
const auto bytes = SysEx7::getDataBytes (packet); | |||
pendingSysExData.insert (pendingSysExData.end(), | |||
bytes.data.begin(), | |||
bytes.data.begin() + bytes.size); | |||
} | |||
void startSysExMessage (double time) | |||
{ | |||
pendingSysExTime = time; | |||
pendingSysExData.push_back (0xf0); | |||
} | |||
template <typename MessageCallback> | |||
void terminateSysExMessage (MessageCallback&& callback) | |||
{ | |||
pendingSysExData.push_back (0xf7); | |||
callback (MidiMessage (pendingSysExData.data(), | |||
int (pendingSysExData.size()), | |||
pendingSysExTime)); | |||
pendingSysExData.clear(); | |||
} | |||
static bool shouldPacketTerminateSysExEarly (uint32_t firstWord) | |||
{ | |||
return ! (isSysExContinuation (firstWord) | |||
|| isSystemRealTime (firstWord) | |||
|| isJROrNOP (firstWord)); | |||
} | |||
static SysEx7::Kind getSysEx7Kind (uint32_t word) | |||
{ | |||
return SysEx7::Kind ((word >> 0x14) & 0xf); | |||
} | |||
static bool isJROrNOP (uint32_t word) | |||
{ | |||
return Utils::getMessageType (word) == 0x0; | |||
} | |||
static bool isSysExContinuation (uint32_t word) | |||
{ | |||
if (Utils::getMessageType (word) != 0x3) | |||
return false; | |||
const auto kind = getSysEx7Kind (word); | |||
return kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end; | |||
} | |||
static bool isSystemRealTime (uint32_t word) | |||
{ | |||
return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8; | |||
} | |||
std::vector<uint8_t> pendingSysExData; | |||
double pendingSysExTime = 0.0; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,195 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processNoteOnOrOff (const HelperValues helpers) | |||
{ | |||
const auto velocity = helpers.byte2; | |||
const auto needsConversion = (helpers.byte0 >> 0x4) == 0x9 && velocity == 0; | |||
const auto firstByte = needsConversion ? (uint8_t) ((0x8 << 0x4) | (helpers.byte0 & 0xf)) | |||
: helpers.byte0; | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, 0), | |||
(uint32_t) (Conversion::scaleTo16 (velocity) << 0x10) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processPolyPressure (const HelperValues helpers) | |||
{ | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, 0), | |||
Conversion::scaleTo32 (helpers.byte2) | |||
}; | |||
} | |||
bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues helpers, | |||
PacketX2& packet) | |||
{ | |||
const auto statusAndChannel = helpers.byte0; | |||
const auto cc = helpers.byte1; | |||
const auto shouldAccumulate = [&] | |||
{ | |||
switch (cc) | |||
{ | |||
case 6: | |||
case 38: | |||
case 98: | |||
case 99: | |||
case 100: | |||
case 101: | |||
return true; | |||
} | |||
return false; | |||
}(); | |||
const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | |||
const auto channel = (uint8_t) (statusAndChannel & 0xf); | |||
const auto byte = helpers.byte2; | |||
if (shouldAccumulate) | |||
{ | |||
auto& accumulator = groupAccumulators[group][channel]; | |||
if (accumulator.addByte (cc, byte)) | |||
{ | |||
const auto& bytes = accumulator.getBytes(); | |||
const auto bank = bytes[0]; | |||
const auto index = bytes[1]; | |||
const auto msb = bytes[2]; | |||
const auto lsb = bytes[3]; | |||
const auto value = (uint16_t) (((msb & 0x7f) << 7) | (lsb & 0x7f)); | |||
const auto newStatus = (uint8_t) (accumulator.getKind() == PnKind::nrpn ? 0x3 : 0x2); | |||
packet = PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, (uint8_t) ((newStatus << 0x4) | channel), bank, index), | |||
Conversion::scaleTo32 (value) | |||
}; | |||
return true; | |||
} | |||
return false; | |||
} | |||
if (cc == 0) | |||
{ | |||
groupBanks[group][channel].setMsb (byte); | |||
return false; | |||
} | |||
if (cc == 32) | |||
{ | |||
groupBanks[group][channel].setLsb (byte); | |||
return false; | |||
} | |||
packet = PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, cc, 0), | |||
Conversion::scaleTo32 (helpers.byte2) | |||
}; | |||
return true; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processProgramChange (const HelperValues helpers) const | |||
{ | |||
const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | |||
const auto channel = (uint8_t) (helpers.byte0 & 0xf); | |||
const auto bank = groupBanks[group][channel]; | |||
const auto valid = bank.isValid(); | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, valid ? 1 : 0), | |||
Utils::bytesToWord (helpers.byte1, 0, valid ? bank.getMsb() : 0, valid ? bank.getLsb() : 0) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processChannelPressure (const HelperValues helpers) | |||
{ | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | |||
Conversion::scaleTo32 (helpers.byte1) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processPitchBend (const HelperValues helpers) | |||
{ | |||
const auto lsb = helpers.byte1; | |||
const auto msb = helpers.byte2; | |||
const auto value = (uint16_t) (msb << 7 | lsb); | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | |||
Conversion::scaleTo32 (value) | |||
}; | |||
} | |||
bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, uint8_t byte) | |||
{ | |||
const auto isStart = cc == 99 || cc == 101; | |||
if (isStart) | |||
{ | |||
kind = cc == 99 ? PnKind::nrpn : PnKind::rpn; | |||
index = 0; | |||
} | |||
bytes[index] = byte; | |||
const auto shouldContinue = [&] | |||
{ | |||
switch (index) | |||
{ | |||
case 0: return isStart; | |||
case 1: return kind == PnKind::nrpn ? cc == 98 : cc == 100; | |||
case 2: return cc == 6; | |||
case 3: return cc == 38; | |||
} | |||
return false; | |||
}(); | |||
index = shouldContinue ? index + 1 : 0; | |||
if (index != bytes.size()) | |||
return false; | |||
index = 0; | |||
return true; | |||
} | |||
} | |||
} |
@@ -1,191 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Translates a series of MIDI 1 Universal MIDI Packets to corresponding MIDI 2 | |||
packets. | |||
@tags{Audio} | |||
*/ | |||
class Midi1ToMidi2DefaultTranslator | |||
{ | |||
public: | |||
Midi1ToMidi2DefaultTranslator() = default; | |||
/** Converts MIDI 1 Universal MIDI Packets to corresponding MIDI 2 packets, | |||
calling `callback` with each converted packet. | |||
In some cases (such as RPN/NRPN messages) multiple MIDI 1 packets will | |||
convert to a single MIDI 2 packet. In these cases, the translator will | |||
accumulate the full message internally, and send a single callback with | |||
the completed message, once all the individual MIDI 1 packets have been | |||
processed. | |||
*/ | |||
template <typename PacketCallback> | |||
void dispatch (const View& v, PacketCallback&& callback) | |||
{ | |||
const auto firstWord = v[0]; | |||
const auto messageType = Utils::getMessageType (firstWord); | |||
if (messageType != 0x2) | |||
{ | |||
callback (v); | |||
return; | |||
} | |||
const HelperValues helperValues | |||
{ | |||
(uint8_t) ((0x4 << 0x4) | Utils::getGroup (firstWord)), | |||
(uint8_t) ((firstWord >> 0x10) & 0xff), | |||
(uint8_t) ((firstWord >> 0x08) & 0x7f), | |||
(uint8_t) ((firstWord >> 0x00) & 0x7f), | |||
}; | |||
switch (Utils::getStatus (firstWord)) | |||
{ | |||
case 0x8: | |||
case 0x9: | |||
{ | |||
const auto packet = processNoteOnOrOff (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xa: | |||
{ | |||
const auto packet = processPolyPressure (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xb: | |||
{ | |||
PacketX2 packet; | |||
if (processControlChange (helperValues, packet)) | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xc: | |||
{ | |||
const auto packet = processProgramChange (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xd: | |||
{ | |||
const auto packet = processChannelPressure (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xe: | |||
{ | |||
const auto packet = processPitchBend (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
} | |||
} | |||
void reset() | |||
{ | |||
groupAccumulators = {}; | |||
groupBanks = {}; | |||
} | |||
private: | |||
enum class PnKind { nrpn, rpn }; | |||
struct HelperValues | |||
{ | |||
uint8_t typeAndGroup; | |||
uint8_t byte0; | |||
uint8_t byte1; | |||
uint8_t byte2; | |||
}; | |||
static PacketX2 processNoteOnOrOff (const HelperValues helpers); | |||
static PacketX2 processPolyPressure (const HelperValues helpers); | |||
bool processControlChange (const HelperValues helpers, PacketX2& packet); | |||
PacketX2 processProgramChange (const HelperValues helpers) const; | |||
static PacketX2 processChannelPressure (const HelperValues helpers); | |||
static PacketX2 processPitchBend (const HelperValues helpers); | |||
class PnAccumulator | |||
{ | |||
public: | |||
bool addByte (uint8_t cc, uint8_t byte); | |||
const std::array<uint8_t, 4>& getBytes() const noexcept { return bytes; } | |||
PnKind getKind() const noexcept { return kind; } | |||
private: | |||
std::array<uint8_t, 4> bytes; | |||
uint8_t index = 0; | |||
PnKind kind = PnKind::nrpn; | |||
}; | |||
class Bank | |||
{ | |||
public: | |||
bool isValid() const noexcept { return ! (msb & 0x80); } | |||
uint8_t getMsb() const noexcept { return msb & 0x7f; } | |||
uint8_t getLsb() const noexcept { return lsb & 0x7f; } | |||
void setMsb (uint8_t i) noexcept { msb = i & 0x7f; } | |||
void setLsb (uint8_t i) noexcept { msb &= 0x7f; lsb = i & 0x7f; } | |||
private: | |||
// We use the top bit to indicate whether this bank is valid. | |||
// After reading the spec, it's not clear how we should determine whether | |||
// there are valid values, so we'll just assume that the bank is valid | |||
// once either the lsb or msb have been written. | |||
uint8_t msb = 0x80; | |||
uint8_t lsb = 0x00; | |||
}; | |||
using ChannelAccumulators = std::array<PnAccumulator, 16>; | |||
std::array<ChannelAccumulators, 16> groupAccumulators; | |||
using ChannelBanks = std::array<Bank, 16>; | |||
std::array<ChannelBanks, 16> groupBanks; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,48 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets. */ | |||
enum class PacketProtocol | |||
{ | |||
MIDI_1_0, | |||
MIDI_2_0, | |||
}; | |||
/** All kinds of MIDI protocol understood by JUCE. */ | |||
enum class MidiProtocol | |||
{ | |||
bytestream, | |||
UMP_MIDI_1_0, | |||
UMP_MIDI_2_0, | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,46 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
A base class for classes which receive Universal MIDI Packets from an input. | |||
@tags{Audio} | |||
*/ | |||
struct Receiver | |||
{ | |||
virtual ~Receiver() noexcept = default; | |||
/** This will be called each time a new packet is ready for processing. */ | |||
virtual void packetReceived (const View& packet, double time) = 0; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,53 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t SysEx7::getNumPacketsRequiredForDataSize (uint32_t size) | |||
{ | |||
constexpr auto denom = 6; | |||
return (size / denom) + ((size % denom) != 0); | |||
} | |||
SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) | |||
{ | |||
const auto numBytes = Utils::getChannel (packet[0]); | |||
constexpr uint8_t maxBytes = 6; | |||
jassert (numBytes <= maxBytes); | |||
return | |||
{ | |||
{ { packet.getU8<2>(), | |||
packet.getU8<3>(), | |||
packet.getU8<4>(), | |||
packet.getU8<5>(), | |||
packet.getU8<6>(), | |||
packet.getU8<7>() } }, | |||
jmin (numBytes, maxBytes) | |||
}; | |||
} | |||
} | |||
} |
@@ -1,77 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
This struct acts as a single-file namespace for Universal MIDI Packet | |||
functionality related to 7-bit SysEx. | |||
@tags{Audio} | |||
*/ | |||
struct SysEx7 | |||
{ | |||
/** Returns the number of 64-bit packets required to hold a series of | |||
SysEx bytes. | |||
The number passed to this function should exclude the leading/trailing | |||
SysEx bytes used in an old midi bytestream, as these are not required | |||
when using Universal MIDI Packets. | |||
*/ | |||
static uint32_t getNumPacketsRequiredForDataSize (uint32_t); | |||
/** The different kinds of UMP SysEx-7 message. */ | |||
enum class Kind : uint8_t | |||
{ | |||
/** The whole message fits in a single 2-word packet. */ | |||
complete = 0, | |||
/** The packet begins a SysEx message that will continue in subsequent packets. */ | |||
begin = 1, | |||
/** The packet is a continuation of an ongoing SysEx message. */ | |||
continuation = 2, | |||
/** The packet terminates an ongoing SysEx message. */ | |||
end = 3 | |||
}; | |||
/** Holds the bytes from a single SysEx-7 packet. */ | |||
struct PacketBytes | |||
{ | |||
std::array<uint8_t, 6> data; | |||
uint8_t size; | |||
}; | |||
/** Extracts the data bytes from a 64-bit data message. */ | |||
static PacketBytes getDataBytes (const PacketX2& packet); | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,59 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t Utils::getNumWordsForMessageType (uint32_t mt) | |||
{ | |||
switch (Utils::getMessageType (mt)) | |||
{ | |||
case 0x0: | |||
case 0x1: | |||
case 0x2: | |||
case 0x6: | |||
case 0x7: | |||
return 1; | |||
case 0x3: | |||
case 0x4: | |||
case 0x8: | |||
case 0x9: | |||
case 0xa: | |||
return 2; | |||
case 0xb: | |||
case 0xc: | |||
return 3; | |||
case 0x5: | |||
case 0xd: | |||
case 0xe: | |||
case 0xf: | |||
return 4; | |||
} | |||
jassertfalse; | |||
return 1; | |||
} | |||
} | |||
} |
@@ -1,117 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Helpful types and functions for interacting with Universal MIDI Packets. | |||
@tags{Audio} | |||
*/ | |||
struct Utils | |||
{ | |||
/** Joins 4 bytes into a single 32-bit word. */ | |||
static constexpr uint32_t bytesToWord (uint8_t a, uint8_t b, uint8_t c, uint8_t d) | |||
{ | |||
return uint32_t (a << 0x18 | b << 0x10 | c << 0x08 | d << 0x00); | |||
} | |||
/** Returns the expected number of 32-bit words in a Universal MIDI Packet, given | |||
the first word of the packet. | |||
The result will be between 1 and 4 inclusive. | |||
A result of 1 means that the word is itself a complete packet. | |||
*/ | |||
static uint32_t getNumWordsForMessageType (uint32_t); | |||
/** | |||
Helper functions for setting/getting 4-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U4 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x1c - Index * 4; | |||
static constexpr uint32_t set (uint32_t word, uint8_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xf << shift)) | (uint32_t) ((value & 0xf) << shift); | |||
} | |||
static constexpr uint8_t get (uint32_t word) | |||
{ | |||
return (uint8_t) ((word >> shift) & 0xf); | |||
} | |||
}; | |||
/** | |||
Helper functions for setting/getting 8-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U8 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x18 - Index * 8; | |||
static constexpr uint32_t set (uint32_t word, uint8_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xff << shift)) | (uint32_t) (value << shift); | |||
} | |||
static constexpr uint8_t get (uint32_t word) | |||
{ | |||
return (uint8_t) ((word >> shift) & 0xff); | |||
} | |||
}; | |||
/** | |||
Helper functions for setting/getting 16-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U16 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x10 - Index * 16; | |||
static constexpr uint32_t set (uint32_t word, uint16_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xffff << shift)) | (uint32_t) (value << shift); | |||
} | |||
static constexpr uint16_t get (uint32_t word) | |||
{ | |||
return (uint16_t) ((word >> shift) & 0xffff); | |||
} | |||
}; | |||
static constexpr uint8_t getMessageType (uint32_t w) noexcept { return U4<0>::get (w); } | |||
static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); } | |||
static constexpr uint8_t getStatus (uint32_t w) noexcept { return U4<2>::get (w); } | |||
static constexpr uint8_t getChannel (uint32_t w) noexcept { return U4<3>::get (w); } | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,35 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t View::size() const noexcept | |||
{ | |||
jassert (ptr != nullptr); | |||
return Utils::getNumWordsForMessageType (*ptr); | |||
} | |||
} | |||
} |
@@ -1,92 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Points to a single Universal MIDI Packet. | |||
The packet must be well-formed for member functions to work correctly. | |||
Specifically, the constructor argument must be the beginning of a region of | |||
uint32_t that contains at least `getNumWordsForMessageType(*ddata)` items, | |||
where `data` is the constructor argument. | |||
NOTE: Instances of this class do not own the memory that they point to! | |||
If you need to store a packet pointed-to by a View for later use, copy | |||
the view contents to a Packets collection, or use the Utils::PacketX types. | |||
@tags{Audio} | |||
*/ | |||
class View | |||
{ | |||
public: | |||
/** Create an invalid view. */ | |||
View() noexcept = default; | |||
/** Create a view of the packet starting at address `d`. */ | |||
explicit View (const uint32_t* data) noexcept : ptr (data) {} | |||
/** Get a pointer to the first word in the Universal MIDI Packet currently | |||
pointed-to by this view. | |||
*/ | |||
const uint32_t* data() const noexcept { return ptr; } | |||
/** Get the number of 32-words (between 1 and 4 inclusive) in the Universal | |||
MIDI Packet currently pointed-to by this view. | |||
*/ | |||
uint32_t size() const noexcept; | |||
/** Get a specific word from this packet. | |||
Passing an `index` that is greater than or equal to the result of `size` | |||
will cause undefined behaviour. | |||
*/ | |||
const uint32_t& operator[] (size_t index) const noexcept { return ptr[index]; } | |||
/** Get an iterator pointing to the first word in the packet. */ | |||
const uint32_t* begin() const noexcept { return ptr; } | |||
const uint32_t* cbegin() const noexcept { return ptr; } | |||
/** Get an iterator pointing one-past the last word in the packet. */ | |||
const uint32_t* end() const noexcept { return ptr + size(); } | |||
const uint32_t* cend() const noexcept { return ptr + size(); } | |||
/** Return true if this view is pointing to the same address as another view. */ | |||
bool operator== (const View& other) const noexcept { return ptr == other.ptr; } | |||
/** Return false if this view is pointing to the same address as another view. */ | |||
bool operator!= (const View& other) const noexcept { return ! operator== (other); } | |||
private: | |||
const uint32_t* ptr = nullptr; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,193 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Holds a single Universal MIDI Packet. | |||
@tags{Audio} | |||
*/ | |||
template <size_t numWords> | |||
class Packet | |||
{ | |||
public: | |||
Packet() = default; | |||
template <size_t w = numWords, typename std::enable_if<w == 1, int>::type = 0> | |||
Packet (uint32_t a) | |||
: contents { { a } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 1); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 2, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b) | |||
: contents { { a, b } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 2); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 3, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b, uint32_t c) | |||
: contents { { a, b, c } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 3); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 4, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b, uint32_t c, uint32_t d) | |||
: contents { { a, b, c, d } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 4); | |||
} | |||
template <size_t w, typename std::enable_if<w == numWords, int>::type = 0> | |||
explicit Packet (const std::array<uint32_t, w>& fullPacket) | |||
: contents (fullPacket) | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (fullPacket.front()) == numWords); | |||
} | |||
Packet withMessageType (uint8_t type) const noexcept | |||
{ | |||
return withU4<0> (type); | |||
} | |||
Packet withGroup (uint8_t group) const noexcept | |||
{ | |||
return withU4<1> (group); | |||
} | |||
Packet withStatus (uint8_t status) const noexcept | |||
{ | |||
return withU4<2> (status); | |||
} | |||
Packet withChannel (uint8_t channel) const noexcept | |||
{ | |||
return withU4<3> (channel); | |||
} | |||
uint8_t getMessageType() const noexcept { return getU4<0>(); } | |||
uint8_t getGroup() const noexcept { return getU4<1>(); } | |||
uint8_t getStatus() const noexcept { return getU4<2>(); } | |||
uint8_t getChannel() const noexcept { return getU4<3>(); } | |||
template <size_t index> | |||
Packet withU4 (uint8_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 8; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U4<index % 8>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU8 (uint8_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 4; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U8<index % 4>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU16 (uint16_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 2; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U16<index % 2>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU32 (uint32_t value) const noexcept | |||
{ | |||
auto copy = *this; | |||
std::get<index> (copy.contents) = value; | |||
return copy; | |||
} | |||
template <size_t index> | |||
uint8_t getU4() const noexcept | |||
{ | |||
return Utils::U4<index % 8>::get (this->template getU32<index / 8>()); | |||
} | |||
template <size_t index> | |||
uint8_t getU8() const noexcept | |||
{ | |||
return Utils::U8<index % 4>::get (this->template getU32<index / 4>()); | |||
} | |||
template <size_t index> | |||
uint16_t getU16() const noexcept | |||
{ | |||
return Utils::U16<index % 2>::get (this->template getU32<index / 2>()); | |||
} | |||
template <size_t index> | |||
uint32_t getU32() const noexcept | |||
{ | |||
return std::get<index> (contents); | |||
} | |||
//============================================================================== | |||
using Contents = std::array<uint32_t, numWords>; | |||
using const_iterator = typename Contents::const_iterator; | |||
const_iterator begin() const noexcept { return contents.begin(); } | |||
const_iterator cbegin() const noexcept { return contents.begin(); } | |||
const_iterator end() const noexcept { return contents.end(); } | |||
const_iterator cend() const noexcept { return contents.end(); } | |||
const uint32_t* data() const noexcept { return contents.data(); } | |||
const uint32_t& front() const noexcept { return contents.front(); } | |||
const uint32_t& back() const noexcept { return contents.back(); } | |||
const uint32_t& operator[] (size_t index) const noexcept { return contents[index]; } | |||
private: | |||
Contents contents { {} }; | |||
}; | |||
using PacketX1 = Packet<1>; | |||
using PacketX2 = Packet<2>; | |||
using PacketX3 = Packet<3>; | |||
using PacketX4 = Packet<4>; | |||
} | |||
} | |||
#endif |
@@ -1,96 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Holds a collection of Universal MIDI Packets. | |||
Unlike MidiBuffer, this collection does not store any additional information | |||
(e.g. timestamps) alongside the raw messages. | |||
If timestamps are required, these can be added to the container in UMP format, | |||
as Jitter Reduction Utility messages. | |||
@tags{Audio} | |||
*/ | |||
class Packets | |||
{ | |||
public: | |||
/** Adds a single packet to the collection. | |||
The View must be valid for this to work. If the view | |||
points to a malformed message, or if the view points to a region | |||
too short for the contained message, this call will result in | |||
undefined behaviour. | |||
*/ | |||
void add (const View& v) { storage.insert (storage.end(), v.cbegin(), v.cend()); } | |||
void add (const PacketX1& p) { addImpl (p); } | |||
void add (const PacketX2& p) { addImpl (p); } | |||
void add (const PacketX3& p) { addImpl (p); } | |||
void add (const PacketX4& p) { addImpl (p); } | |||
/** Pre-allocates space for at least `numWords` 32-bit words in this collection. */ | |||
void reserve (size_t numWords) { storage.reserve (numWords); } | |||
/** Removes all previously-added packets from this collection. */ | |||
void clear() { storage.clear(); } | |||
/** Gets an iterator pointing to the first packet in this collection. */ | |||
Iterator cbegin() const noexcept { return Iterator (data(), size()); } | |||
Iterator begin() const noexcept { return cbegin(); } | |||
/** Gets an iterator pointing one-past the last packet in this collection. */ | |||
Iterator cend() const noexcept { return Iterator (data() + size(), 0); } | |||
Iterator end() const noexcept { return cend(); } | |||
/** Gets a pointer to the contents of the collection as a range of raw 32-bit words. */ | |||
const uint32_t* data() const noexcept { return storage.data(); } | |||
/** Returns the number of uint32_t words in storage. | |||
Note that this is likely to be larger than the number of packets | |||
currently being stored, as some packets span multiple words. | |||
*/ | |||
size_t size() const noexcept { return storage.size(); } | |||
private: | |||
template <size_t numWords> | |||
void addImpl (const Packet<numWords>& p) | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (p[0]) == numWords); | |||
add (View (p.data())); | |||
} | |||
std::vector<uint32_t> storage; | |||
}; | |||
} | |||
} | |||
#endif |
@@ -1,426 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
This class represents an instrument handling MPE. | |||
It has an MPE zone layout and maintains a state of currently | |||
active (playing) notes and the values of their dimensions of expression. | |||
You can trigger and modulate notes: | |||
- by passing MIDI messages with the method processNextMidiEvent; | |||
- by directly calling the methods noteOn, noteOff etc. | |||
The class implements the channel and note management logic specified in | |||
MPE. If you pass it a message, it will know what notes on what | |||
channels (if any) should be affected by that message. | |||
The class has a Listener class that can be used to react to note and | |||
state changes and trigger some functionality for your application. | |||
For example, you can use this class to write an MPE visualiser. | |||
If you want to write a real-time audio synth with MPE functionality, | |||
you should instead use the classes MPESynthesiserBase, which adds | |||
the ability to render audio and to manage voices. | |||
@see MPENote, MPEZoneLayout, MPESynthesiser | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MPEInstrument | |||
{ | |||
public: | |||
/** Constructor. | |||
This will construct an MPE instrument with inactive lower and upper zones. | |||
In order to process incoming MIDI messages call setZoneLayout, use the MPEZoneLayout | |||
constructor, define the layout via MIDI RPN messages, or set the instrument to legacy mode. | |||
*/ | |||
MPEInstrument() noexcept; | |||
/** Constructs an MPE instrument with the specified zone layout. */ | |||
MPEInstrument (MPEZoneLayout layout); | |||
/** Destructor. */ | |||
virtual ~MPEInstrument(); | |||
//============================================================================== | |||
/** Returns the current zone layout of the instrument. | |||
This happens by value, to enforce thread-safety and class invariants. | |||
Note: If the instrument is in legacy mode, the return value of this | |||
method is unspecified. | |||
*/ | |||
MPEZoneLayout getZoneLayout() const noexcept; | |||
/** Re-sets the zone layout of the instrument to the one passed in. | |||
As a side effect, this will discard all currently playing notes, | |||
and call noteReleased for all of them. | |||
This will also disable legacy mode in case it was enabled previously. | |||
*/ | |||
void setZoneLayout (MPEZoneLayout newLayout); | |||
/** Returns true if the given MIDI channel (1-16) is a note channel in any | |||
of the MPEInstrument's MPE zones; false otherwise. | |||
When in legacy mode, this will return true if the given channel is | |||
contained in the current legacy mode channel range; false otherwise. | |||
*/ | |||
bool isMemberChannel (int midiChannel) const noexcept; | |||
/** Returns true if the given MIDI channel (1-16) is a master channel (channel | |||
1 or 16). | |||
In legacy mode, this will always return false. | |||
*/ | |||
bool isMasterChannel (int midiChannel) const noexcept; | |||
/** Returns true if the given MIDI channel (1-16) is used by any of the | |||
MPEInstrument's MPE zones; false otherwise. | |||
When in legacy mode, this will return true if the given channel is | |||
contained in the current legacy mode channel range; false otherwise. | |||
*/ | |||
bool isUsingChannel (int midiChannel) const noexcept; | |||
//============================================================================== | |||
/** The MPE note tracking mode. In case there is more than one note playing | |||
simultaneously on the same MIDI channel, this determines which of these | |||
notes will be modulated by an incoming MPE message on that channel | |||
(pressure, pitchbend, or timbre). | |||
The default is lastNotePlayedOnChannel. | |||
*/ | |||
enum TrackingMode | |||
{ | |||
lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */ | |||
lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */ | |||
highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */ | |||
allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */ | |||
}; | |||
/** Set the MPE tracking mode for the pressure dimension. */ | |||
void setPressureTrackingMode (TrackingMode modeToUse); | |||
/** Set the MPE tracking mode for the pitchbend dimension. */ | |||
void setPitchbendTrackingMode (TrackingMode modeToUse); | |||
/** Set the MPE tracking mode for the timbre dimension. */ | |||
void setTimbreTrackingMode (TrackingMode modeToUse); | |||
//============================================================================== | |||
/** Process a MIDI message and trigger the appropriate method calls | |||
(noteOn, noteOff etc.) | |||
You can override this method if you need some special MIDI message | |||
treatment on top of the standard MPE logic implemented here. | |||
*/ | |||
virtual void processNextMidiEvent (const MidiMessage& message); | |||
//============================================================================== | |||
/** Request a note-on on the given channel, with the given initial note | |||
number and velocity. | |||
If the message arrives on a valid note channel, this will create a | |||
new MPENote and call the noteAdded callback. | |||
*/ | |||
virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity); | |||
/** Request a note-off. | |||
If there is a matching playing note, this will release the note | |||
(except if it is sustained by a sustain or sostenuto pedal) and call | |||
the noteReleased callback. | |||
*/ | |||
virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity); | |||
/** Request a pitchbend on the given channel with the given value (in units | |||
of MIDI pitchwheel position). | |||
Internally, this will determine whether the pitchwheel move is a | |||
per-note pitchbend or a master pitchbend (depending on midiChannel), | |||
take the correct per-note or master pitchbend range of the affected MPE | |||
zone, and apply the resulting pitchbend to the affected note(s) (if any). | |||
*/ | |||
virtual void pitchbend (int midiChannel, MPEValue pitchbend); | |||
/** Request a pressure change on the given channel with the given value. | |||
This will modify the pressure dimension of the note currently held down | |||
on this channel (if any). If the channel is a zone master channel, | |||
the pressure change will be broadcast to all notes in this zone. | |||
*/ | |||
virtual void pressure (int midiChannel, MPEValue value); | |||
/** Request a third dimension (timbre) change on the given channel with the | |||
given value. | |||
This will modify the timbre dimension of the note currently held down | |||
on this channel (if any). If the channel is a zone master channel, | |||
the timbre change will be broadcast to all notes in this zone. | |||
*/ | |||
virtual void timbre (int midiChannel, MPEValue value); | |||
/** Request a poly-aftertouch change for a given note number. | |||
The change will be broadcast to all notes sharing the channel and note | |||
number of the change message. | |||
*/ | |||
virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value); | |||
/** Request a sustain pedal press or release. | |||
If midiChannel is a zone's master channel, this will act on all notes in | |||
that zone; otherwise, nothing will happen. | |||
*/ | |||
virtual void sustainPedal (int midiChannel, bool isDown); | |||
/** Request a sostenuto pedal press or release. | |||
If midiChannel is a zone's master channel, this will act on all notes in | |||
that zone; otherwise, nothing will happen. | |||
*/ | |||
virtual void sostenutoPedal (int midiChannel, bool isDown); | |||
/** Discard all currently playing notes. | |||
This will also call the noteReleased listener callback for all of them. | |||
*/ | |||
void releaseAllNotes(); | |||
//============================================================================== | |||
/** Returns the number of MPE notes currently played by the instrument. */ | |||
int getNumPlayingNotes() const noexcept; | |||
/** Returns the note at the given index. | |||
If there is no such note, returns an invalid MPENote. The notes are sorted | |||
such that the most recently added note is the last element. | |||
*/ | |||
MPENote getNote (int index) const noexcept; | |||
/** Returns the note currently playing on the given midiChannel with the | |||
specified initial MIDI note number, if there is such a note. Otherwise, | |||
this returns an invalid MPENote (check with note.isValid() before use!) | |||
*/ | |||
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; | |||
/** Returns the note with a given ID. */ | |||
MPENote getNoteWithID (uint16 noteID) 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 MPE notes played | |||
by this instrument, and any changes to its zone layout. | |||
Note: This listener type receives its callbacks immediately, and not | |||
via the message thread (so you might be for example in the MIDI thread). | |||
Therefore you should never do heavy work such as graphics rendering etc. | |||
inside those callbacks. | |||
*/ | |||
class JUCE_API Listener | |||
{ | |||
public: | |||
/** Destructor. */ | |||
virtual ~Listener() = default; | |||
/** Implement this callback to be informed whenever a new expressive MIDI | |||
note is triggered. | |||
*/ | |||
virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); } | |||
/** Implement this callback to be informed whenever a currently playing | |||
MPE note's pressure value changes. | |||
*/ | |||
virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||
/** Implement this callback to be informed whenever a currently playing | |||
MPE note's pitchbend value changes. | |||
Note: This can happen if the note itself is bent, if there is a | |||
master channel pitchbend event, or if both occur simultaneously. | |||
Call MPENote::getFrequencyInHertz to get the effective note frequency. | |||
*/ | |||
virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||
/** Implement this callback to be informed whenever a currently playing | |||
MPE note's timbre value changes. | |||
*/ | |||
virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||
/** Implement this callback to be informed whether a currently playing | |||
MPE note's key state (whether the key is down and/or the note is | |||
sustained) has changed. | |||
Note: If the key state changes to MPENote::off, noteReleased is | |||
called instead. | |||
*/ | |||
virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); } | |||
/** Implement this callback to be informed whenever an MPE note | |||
is released (either by a note-off message, or by a sustain/sostenuto | |||
pedal release for a note that already received a note-off), | |||
and should therefore stop playing. | |||
*/ | |||
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } | |||
/** Implement this callback to be informed whenever the MPE zone layout | |||
or legacy mode settings of this instrument have been changed. | |||
*/ | |||
virtual void zoneLayoutChanged() {} | |||
}; | |||
//============================================================================== | |||
/** Adds a listener. */ | |||
void addListener (Listener* listenerToAdd); | |||
/** Removes a listener. */ | |||
void removeListener (Listener* listenerToRemove); | |||
//============================================================================== | |||
/** Puts the instrument into legacy mode. If legacy mode is already enabled this method | |||
does nothing. | |||
As a side effect, this will discard all currently playing notes, | |||
and call noteReleased for all of them. | |||
This special zone layout mode is for backwards compatibility with | |||
non-MPE MIDI devices. In this mode, the instrument will ignore the | |||
current MPE zone layout. It will instead take a range of MIDI channels | |||
(default: all channels 1-16) and treat them as note channels, with no | |||
master channel. MIDI channels outside of this range will be ignored. | |||
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||
Must be between 0 and 96, otherwise behaviour is undefined. | |||
The default pitchbend range in legacy mode is +/- 2 semitones. | |||
@param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||
The default is to use all MIDI channels (1-16). | |||
To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||
*/ | |||
void enableLegacyMode (int pitchbendRange = 2, | |||
Range<int> channelRange = Range<int> (1, 17)); | |||
/** Returns true if the instrument is in legacy mode, false otherwise. */ | |||
bool isLegacyModeEnabled() const noexcept; | |||
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
Range<int> getLegacyModeChannelRange() const noexcept; | |||
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
void setLegacyModeChannelRange (Range<int> channelRange); | |||
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
int getLegacyModePitchbendRange() const noexcept; | |||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
void setLegacyModePitchbendRange (int pitchbendRange); | |||
protected: | |||
//============================================================================== | |||
CriticalSection lock; | |||
private: | |||
//============================================================================== | |||
Array<MPENote> notes; | |||
MPEZoneLayout zoneLayout; | |||
ListenerList<Listener> listeners; | |||
uint8 lastPressureLowerBitReceivedOnChannel[16]; | |||
uint8 lastTimbreLowerBitReceivedOnChannel[16]; | |||
bool isMemberChannelSustained[16]; | |||
struct LegacyMode | |||
{ | |||
bool isEnabled = false; | |||
Range<int> channelRange; | |||
int pitchbendRange = 2; | |||
}; | |||
struct MPEDimension | |||
{ | |||
TrackingMode trackingMode = lastNotePlayedOnChannel; | |||
MPEValue lastValueReceivedOnChannel[16]; | |||
MPEValue MPENote::* value; | |||
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } | |||
}; | |||
LegacyMode legacyMode; | |||
MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | |||
void resetLastReceivedValues(); | |||
void updateDimension (int midiChannel, MPEDimension&, MPEValue); | |||
void updateDimensionMaster (bool, MPEDimension&, MPEValue); | |||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||
void callListenersDimensionChanged (const MPENote&, const MPEDimension&); | |||
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||
void processMidiNoteOnMessage (const MidiMessage&); | |||
void processMidiNoteOffMessage (const MidiMessage&); | |||
void processMidiPitchWheelMessage (const MidiMessage&); | |||
void processMidiChannelPressureMessage (const MidiMessage&); | |||
void processMidiControllerMessage (const MidiMessage&); | |||
void processMidiResetAllControllersMessage (const MidiMessage&); | |||
void processMidiAfterTouchMessage (const MidiMessage&); | |||
void handlePressureMSB (int midiChannel, int value) noexcept; | |||
void handlePressureLSB (int midiChannel, int value) noexcept; | |||
void handleTimbreMSB (int midiChannel, int value) noexcept; | |||
void handleTimbreLSB (int midiChannel, int value) noexcept; | |||
void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto); | |||
const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept; | |||
MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept; | |||
const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept; | |||
MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept; | |||
const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept; | |||
MPENote* getLastNotePlayedPtr (int midiChannel) noexcept; | |||
const MPENote* getHighestNotePtr (int midiChannel) const noexcept; | |||
MPENote* getHighestNotePtr (int midiChannel) noexcept; | |||
const MPENote* getLowestNotePtr (int midiChannel) const noexcept; | |||
MPENote* getLowestNotePtr (int midiChannel) noexcept; | |||
void updateNoteTotalPitchbend (MPENote&); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument) | |||
}; | |||
} // namespace juce |
@@ -1,238 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MidiBuffer MPEMessages::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) | |||
{ | |||
auto buffer = MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); | |||
buffer.addEvents (setLowerZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); | |||
buffer.addEvents (setLowerZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); | |||
return buffer; | |||
} | |||
MidiBuffer MPEMessages::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) | |||
{ | |||
auto buffer = MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); | |||
buffer.addEvents (setUpperZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); | |||
buffer.addEvents (setUpperZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); | |||
return buffer; | |||
} | |||
MidiBuffer MPEMessages::setLowerZonePerNotePitchbendRange (int perNotePitchbendRange) | |||
{ | |||
return MidiRPNGenerator::generate (2, 0, perNotePitchbendRange, false, false); | |||
} | |||
MidiBuffer MPEMessages::setUpperZonePerNotePitchbendRange (int perNotePitchbendRange) | |||
{ | |||
return MidiRPNGenerator::generate (15, 0, perNotePitchbendRange, false, false); | |||
} | |||
MidiBuffer MPEMessages::setLowerZoneMasterPitchbendRange (int masterPitchbendRange) | |||
{ | |||
return MidiRPNGenerator::generate (1, 0, masterPitchbendRange, false, false); | |||
} | |||
MidiBuffer MPEMessages::setUpperZoneMasterPitchbendRange (int masterPitchbendRange) | |||
{ | |||
return MidiRPNGenerator::generate (16, 0, masterPitchbendRange, false, false); | |||
} | |||
MidiBuffer MPEMessages::clearLowerZone() | |||
{ | |||
return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 0, false, false); | |||
} | |||
MidiBuffer MPEMessages::clearUpperZone() | |||
{ | |||
return MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, 0, false, false); | |||
} | |||
MidiBuffer MPEMessages::clearAllZones() | |||
{ | |||
MidiBuffer buffer; | |||
buffer.addEvents (clearLowerZone(), 0, -1, 0); | |||
buffer.addEvents (clearUpperZone(), 0, -1, 0); | |||
return buffer; | |||
} | |||
MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout) | |||
{ | |||
MidiBuffer buffer; | |||
buffer.addEvents (clearAllZones(), 0, -1, 0); | |||
auto lowerZone = layout.getLowerZone(); | |||
if (lowerZone.isActive()) | |||
buffer.addEvents (setLowerZone (lowerZone.numMemberChannels, | |||
lowerZone.perNotePitchbendRange, | |||
lowerZone.masterPitchbendRange), | |||
0, -1, 0); | |||
auto upperZone = layout.getUpperZone(); | |||
if (upperZone.isActive()) | |||
buffer.addEvents (setUpperZone (upperZone.numMemberChannels, | |||
upperZone.perNotePitchbendRange, | |||
upperZone.masterPitchbendRange), | |||
0, -1, 0); | |||
return buffer; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MPEMessagesTests : public UnitTest | |||
{ | |||
public: | |||
MPEMessagesTests() | |||
: UnitTest ("MPEMessages class", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("add zone"); | |||
{ | |||
{ | |||
MidiBuffer buffer = MPEMessages::setLowerZone (7); | |||
const uint8 expectedBytes[] = | |||
{ | |||
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set up zone | |||
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48) | |||
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02 // master pbrange (default = 2) | |||
}; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
{ | |||
MidiBuffer buffer = MPEMessages::setUpperZone (5, 96, 0); | |||
const uint8 expectedBytes[] = | |||
{ | |||
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x05, // set up zone | |||
0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x60, // per-note pbrange (custom) | |||
0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // master pbrange (custom) | |||
}; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
} | |||
beginTest ("set per-note pitchbend range"); | |||
{ | |||
MidiBuffer buffer = MPEMessages::setLowerZonePerNotePitchbendRange (96); | |||
const uint8 expectedBytes[] = { 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60 }; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
beginTest ("set master pitchbend range"); | |||
{ | |||
MidiBuffer buffer = MPEMessages::setUpperZoneMasterPitchbendRange (60); | |||
const uint8 expectedBytes[] = { 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x3c }; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
beginTest ("clear all zones"); | |||
{ | |||
MidiBuffer buffer = MPEMessages::clearAllZones(); | |||
const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone | |||
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // clear upper zone | |||
}; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
beginTest ("set complete state"); | |||
{ | |||
MPEZoneLayout layout; | |||
layout.setLowerZone (7, 96, 0); | |||
layout.setUpperZone (7); | |||
MidiBuffer buffer = MPEMessages::setZoneLayout (layout); | |||
const uint8 expectedBytes[] = { | |||
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone | |||
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00, // clear upper zone | |||
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set lower zone | |||
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60, // per-note pbrange (custom) | |||
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // master pbrange (custom) | |||
0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x07, // set upper zone | |||
0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x30, // per-note pbrange (default = 48) | |||
0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x02 // master pbrange (default = 2) | |||
}; | |||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||
} | |||
} | |||
private: | |||
//============================================================================== | |||
void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) | |||
{ | |||
uint8 actualBytes[128] = { 0 }; | |||
extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes)); | |||
expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); | |||
} | |||
//============================================================================== | |||
void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) | |||
{ | |||
std::size_t pos = 0; | |||
for (const auto metadata : midiBuffer) | |||
{ | |||
const uint8* data = metadata.data; | |||
std::size_t dataSize = (std::size_t) metadata.numBytes; | |||
if (pos + dataSize > maxBytes) | |||
return; | |||
std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); | |||
pos += dataSize; | |||
} | |||
} | |||
}; | |||
static MPEMessagesTests MPEMessagesUnitTests; | |||
#endif | |||
} // namespace juce |
@@ -1,116 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
This helper class contains the necessary helper functions to generate | |||
MIDI messages that are exclusive to MPE, such as defining the upper and lower | |||
MPE zones and setting per-note and master pitchbend ranges. | |||
You can then send them to your MPE device using MidiOutput::sendBlockOfMessagesNow. | |||
All other MPE messages like per-note pitchbend, pressure, and third | |||
dimension, are ordinary MIDI messages that should be created using the MidiMessage | |||
class instead. You just need to take care to send them to the appropriate | |||
per-note MIDI channel. | |||
Note: If you are working with an MPEZoneLayout object inside your app, | |||
you should not use the message sequences provided here. Instead, you should | |||
change the zone layout programmatically with the member functions provided in the | |||
MPEZoneLayout class itself. You should also make sure that the Expressive | |||
MIDI zone layout of your C++ code and of the MPE device are kept in sync. | |||
@see MidiMessage, MPEZoneLayout | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MPEMessages | |||
{ | |||
public: | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the lower MPE zone. | |||
*/ | |||
static MidiBuffer setLowerZone (int numMemberChannels = 0, | |||
int perNotePitchbendRange = 48, | |||
int masterPitchbendRange = 2); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the upper MPE zone. | |||
*/ | |||
static MidiBuffer setUpperZone (int numMemberChannels = 0, | |||
int perNotePitchbendRange = 48, | |||
int masterPitchbendRange = 2); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the per-note pitchbend range of the lower MPE zone. | |||
*/ | |||
static MidiBuffer setLowerZonePerNotePitchbendRange (int perNotePitchbendRange = 48); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the per-note pitchbend range of the upper MPE zone. | |||
*/ | |||
static MidiBuffer setUpperZonePerNotePitchbendRange (int perNotePitchbendRange = 48); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the master pitchbend range of the lower MPE zone. | |||
*/ | |||
static MidiBuffer setLowerZoneMasterPitchbendRange (int masterPitchbendRange = 2); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will set the master pitchbend range of the upper MPE zone. | |||
*/ | |||
static MidiBuffer setUpperZoneMasterPitchbendRange (int masterPitchbendRange = 2); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will clear the lower zone. | |||
*/ | |||
static MidiBuffer clearLowerZone(); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will clear the upper zone. | |||
*/ | |||
static MidiBuffer clearUpperZone(); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will clear the lower and upper zones. | |||
*/ | |||
static MidiBuffer clearAllZones(); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will reset the whole MPE zone layout of the | |||
device to the layout passed in. This will first clear the current lower and upper | |||
zones, then then set the zones contained in the passed-in zone layout, and set their | |||
per-note and master pitchbend ranges to their current values. | |||
*/ | |||
static MidiBuffer setZoneLayout (MPEZoneLayout layout); | |||
/** The RPN number used for MPE zone layout messages. | |||
Pitchbend range messages (both per-note and master) are instead sent | |||
on RPN 0 as in standard MIDI 1.0. | |||
*/ | |||
static const int zoneLayoutMessagesRpnNumber = 6; | |||
}; | |||
} // namespace juce |
@@ -1,127 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace | |||
{ | |||
uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); | |||
return uint16 ((midiChannel << 7) + midiNoteNumber); | |||
} | |||
} | |||
//============================================================================== | |||
MPENote::MPENote (int midiChannel_, | |||
int initialNote_, | |||
MPEValue noteOnVelocity_, | |||
MPEValue pitchbend_, | |||
MPEValue pressure_, | |||
MPEValue timbre_, | |||
KeyState keyState_) noexcept | |||
: noteID (generateNoteID (midiChannel_, initialNote_)), | |||
midiChannel (uint8 (midiChannel_)), | |||
initialNote (uint8 (initialNote_)), | |||
noteOnVelocity (noteOnVelocity_), | |||
pitchbend (pitchbend_), | |||
pressure (pressure_), | |||
initialTimbre (timbre_), | |||
timbre (timbre_), | |||
keyState (keyState_) | |||
{ | |||
jassert (keyState != MPENote::off); | |||
jassert (isValid()); | |||
} | |||
MPENote::MPENote() noexcept {} | |||
//============================================================================== | |||
bool MPENote::isValid() const noexcept | |||
{ | |||
return midiChannel > 0 && midiChannel <= 16 && initialNote < 128; | |||
} | |||
//============================================================================== | |||
double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept | |||
{ | |||
auto pitchInSemitones = double (initialNote) + totalPitchbendInSemitones; | |||
return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0); | |||
} | |||
//============================================================================== | |||
bool MPENote::operator== (const MPENote& other) const noexcept | |||
{ | |||
jassert (isValid() && other.isValid()); | |||
return noteID == other.noteID; | |||
} | |||
bool MPENote::operator!= (const MPENote& other) const noexcept | |||
{ | |||
jassert (isValid() && other.isValid()); | |||
return noteID != other.noteID; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MPENoteTests : public UnitTest | |||
{ | |||
public: | |||
MPENoteTests() | |||
: UnitTest ("MPENote class", UnitTestCategories::midi) | |||
{} | |||
//============================================================================== | |||
void runTest() override | |||
{ | |||
beginTest ("getFrequencyInHertz"); | |||
{ | |||
MPENote note; | |||
note.initialNote = 60; | |||
note.totalPitchbendInSemitones = -0.5; | |||
expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); | |||
} | |||
} | |||
private: | |||
//============================================================================== | |||
void expectEqualsWithinOneCent (double frequencyInHertzActual, | |||
double frequencyInHertzExpected) | |||
{ | |||
double ratio = frequencyInHertzActual / frequencyInHertzExpected; | |||
double oneCent = 1.0005946; | |||
expect (ratio < oneCent); | |||
expect (ratio > 1.0 / oneCent); | |||
} | |||
}; | |||
static MPENoteTests MPENoteUnitTests; | |||
#endif | |||
} // namespace juce |
@@ -1,184 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
This struct represents a playing MPE note. | |||
A note is identified by a unique ID, or alternatively, by a MIDI channel | |||
and an initial note. It is characterised by five dimensions of continuous | |||
expressive control. Their current values are represented as | |||
MPEValue objects. | |||
@see MPEValue | |||
@tags{Audio} | |||
*/ | |||
struct JUCE_API MPENote | |||
{ | |||
//============================================================================== | |||
/** Possible values for the note key state. */ | |||
enum KeyState | |||
{ | |||
off = 0, /**< The key is up (off). */ | |||
keyDown = 1, /**< The note key is currently down (pressed). */ | |||
sustained = 2, /**< The note is sustained (by a sustain or sostenuto pedal). */ | |||
keyDownAndSustained = 3 /**< The note key is down and sustained (by a sustain or sostenuto pedal). */ | |||
}; | |||
//============================================================================== | |||
/** Constructor. | |||
@param midiChannel The MIDI channel of the note, between 2 and 15. | |||
(Channel 1 and channel 16 can never be note channels in MPE). | |||
@param initialNote The MIDI note number, between 0 and 127. | |||
@param velocity The note-on velocity of the note. | |||
@param pitchbend The initial per-note pitchbend of the note. | |||
@param pressure The initial pressure of the note. | |||
@param timbre The timbre value of the note. | |||
@param keyState The key state of the note (whether the key is down | |||
and/or the note is sustained). This value must not | |||
be MPENote::off, since you are triggering a new note. | |||
(If not specified, the default value will be MPENote::keyDown.) | |||
*/ | |||
MPENote (int midiChannel, | |||
int initialNote, | |||
MPEValue velocity, | |||
MPEValue pitchbend, | |||
MPEValue pressure, | |||
MPEValue timbre, | |||
KeyState keyState = MPENote::keyDown) noexcept; | |||
/** Default constructor. | |||
Constructs an invalid MPE note (a note with the key state MPENote::off | |||
and an invalid MIDI channel. The only allowed use for such a note is to | |||
call isValid() on it; everything else is undefined behaviour. | |||
*/ | |||
MPENote() noexcept; | |||
/** Checks whether the MPE note is valid. */ | |||
bool isValid() const noexcept; | |||
//============================================================================== | |||
// Invariants that define the note. | |||
/** A unique ID. Useful to distinguish the note from other simultaneously | |||
sounding notes that may use the same note number or MIDI channel. | |||
This should never change during the lifetime of a note object. | |||
*/ | |||
uint16 noteID = 0; | |||
/** The MIDI channel which this note uses. | |||
This should never change during the lifetime of an MPENote object. | |||
*/ | |||
uint8 midiChannel = 0; | |||
/** The MIDI note number that was sent when the note was triggered. | |||
This should never change during the lifetime of an MPENote object. | |||
*/ | |||
uint8 initialNote = 0; | |||
//============================================================================== | |||
// The five dimensions of continuous expressive control | |||
/** The velocity ("strike") of the note-on. | |||
This dimension will stay constant after the note has been turned on. | |||
*/ | |||
MPEValue noteOnVelocity { MPEValue::minValue() }; | |||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel | |||
position). This dimension can be modulated while the note sounds. | |||
Note: This value is not aware of the currently used pitchbend range, | |||
or an additional master pitchbend that may be simultaneously applied. | |||
To compute the actual effective pitchbend of an MPENote, you should | |||
probably use the member totalPitchbendInSemitones instead. | |||
@see totalPitchbendInSemitones, getFrequencyInHertz | |||
*/ | |||
MPEValue pitchbend { MPEValue::centreValue() }; | |||
/** Current pressure with which the note is held down. | |||
This dimension can be modulated while the note sounds. | |||
*/ | |||
MPEValue pressure { MPEValue::centreValue() }; | |||
/** Initial value of timbre when the note was triggered. | |||
This should never change during the lifetime of an MPENote object. | |||
*/ | |||
MPEValue initialTimbre { MPEValue::centreValue() }; | |||
/** Current value of the note's third expressive dimension, typically | |||
encoding some kind of timbre parameter. | |||
This dimension can be modulated while the note sounds. | |||
*/ | |||
MPEValue timbre { MPEValue::centreValue() }; | |||
/** The release velocity ("lift") of the note after a note-off has been | |||
received. | |||
This dimension will only have a meaningful value after a note-off has | |||
been received for the note (and keyState is set to MPENote::off or | |||
MPENote::sustained). Initially, the value is undefined. | |||
*/ | |||
MPEValue noteOffVelocity { MPEValue::minValue() }; | |||
//============================================================================== | |||
/** Current effective pitchbend of the note in units of semitones, relative | |||
to initialNote. You should use this to compute the actual effective pitch | |||
of the note. This value is computed and set by an MPEInstrument to the | |||
sum of the per-note pitchbend value (stored in MPEValue::pitchbend) | |||
and the master pitchbend of the MPE zone, weighted with the per-note | |||
pitchbend range and master pitchbend range of the zone, respectively. | |||
@see getFrequencyInHertz | |||
*/ | |||
double totalPitchbendInSemitones; | |||
/** Current key state. Indicates whether the note key is currently down (pressed) | |||
and/or the note is sustained (by a sustain or sostenuto pedal). | |||
*/ | |||
KeyState keyState { MPENote::off }; | |||
//============================================================================== | |||
/** Returns the current frequency of the note in Hertz. This is the sum of | |||
the initialNote and the totalPitchbendInSemitones, converted to Hertz. | |||
*/ | |||
double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept; | |||
/** Returns true if two notes are the same, determined by their unique ID. */ | |||
bool operator== (const MPENote& other) const noexcept; | |||
/** Returns true if two notes are different notes, determined by their unique ID. */ | |||
bool operator!= (const MPENote& other) const noexcept; | |||
}; | |||
} // namespace juce |
@@ -1,341 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MPESynthesiser::MPESynthesiser() | |||
{ | |||
} | |||
MPESynthesiser::MPESynthesiser (MPEInstrument& mpeInstrument) | |||
: MPESynthesiserBase (mpeInstrument) | |||
{ | |||
} | |||
MPESynthesiser::~MPESynthesiser() | |||
{ | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) | |||
{ | |||
jassert (voice != nullptr); | |||
voice->currentlyPlayingNote = noteToStart; | |||
voice->noteOnTime = lastNoteOnCounter++; | |||
voice->noteStarted(); | |||
} | |||
void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) | |||
{ | |||
jassert (voice != nullptr); | |||
voice->currentlyPlayingNote = noteToStop; | |||
voice->noteStopped (allowTailOff); | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::noteAdded (MPENote newNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
if (auto* voice = findFreeVoice (newNote, shouldStealVoices)) | |||
startVoice (voice, newNote); | |||
} | |||
void MPESynthesiser::notePressureChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->notePressureChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::notePitchbendChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->notePitchbendChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteTimbreChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->noteTimbreChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->noteKeyStateChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteReleased (MPENote finishedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto i = voices.size(); --i >= 0;) | |||
{ | |||
auto* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote (finishedNote)) | |||
stopVoice (voice, finishedNote, true); | |||
} | |||
} | |||
void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||
{ | |||
MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate); | |||
const ScopedLock sl (voicesLock); | |||
turnOffAllVoices (false); | |||
for (auto i = voices.size(); --i >= 0;) | |||
voices.getUnchecked (i)->setCurrentSampleRate (newRate); | |||
} | |||
void MPESynthesiser::handleMidiEvent (const MidiMessage& m) | |||
{ | |||
if (m.isController()) | |||
handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); | |||
else if (m.isProgramChange()) | |||
handleProgramChange (m.getChannel(), m.getProgramChangeNumber()); | |||
MPESynthesiserBase::handleMidiEvent (m); | |||
} | |||
MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (! voice->isActive()) | |||
return voice; | |||
} | |||
if (stealIfNoneAvailable) | |||
return findVoiceToSteal (noteToFindVoiceFor); | |||
return nullptr; | |||
} | |||
MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const | |||
{ | |||
// This voice-stealing algorithm applies the following heuristics: | |||
// - Re-use the oldest notes first | |||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||
// apparently you are trying to render audio without having any voices... | |||
jassert (voices.size() > 0); | |||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||
MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
// this is a list of voices we can steal, sorted by how long they've been running | |||
Array<MPESynthesiserVoice*> usableVoices; | |||
usableVoices.ensureStorageAllocated (voices.size()); | |||
for (auto* voice : voices) | |||
{ | |||
jassert (voice->isActive()); // We wouldn't be here otherwise | |||
usableVoices.add (voice); | |||
// NB: Using a functor rather than a lambda here due to scare-stories about | |||
// compilers generating code containing heap allocations.. | |||
struct Sorter | |||
{ | |||
bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; } | |||
}; | |||
std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); | |||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
{ | |||
auto noteNumber = voice->getCurrentlyPlayingNote().initialNote; | |||
if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote) | |||
low = voice; | |||
if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote) | |||
top = voice; | |||
} | |||
} | |||
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||
if (top == low) | |||
top = nullptr; | |||
// If we want to re-use the voice to trigger a new note, | |||
// then The oldest note that's playing the same note number is ideal. | |||
if (noteToStealVoiceFor.isValid()) | |||
for (auto* voice : usableVoices) | |||
if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote) | |||
return voice; | |||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||
for (auto* voice : usableVoices) | |||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
return voice; | |||
// Oldest voice that doesn't have a finger on it: | |||
for (auto* voice : usableVoices) | |||
if (voice != low && voice != top | |||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown | |||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) | |||
return voice; | |||
// Oldest voice that isn't protected | |||
for (auto* voice : usableVoices) | |||
if (voice != low && voice != top) | |||
return voice; | |||
// We've only got "protected" voices now: lowest note takes priority | |||
jassert (low != nullptr); | |||
// Duophonic synth: give priority to the bass note: | |||
if (top != nullptr) | |||
return top; | |||
return low; | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
newVoice->setCurrentSampleRate (getSampleRate()); | |||
voices.add (newVoice); | |||
} | |||
void MPESynthesiser::clearVoices() | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
voices.clear(); | |||
} | |||
MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
return voices [index]; | |||
} | |||
void MPESynthesiser::removeVoice (const int index) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
voices.remove (index); | |||
} | |||
void MPESynthesiser::reduceNumVoices (const int newNumVoices) | |||
{ | |||
// we can't possibly get to a negative number of voices... | |||
jassert (newNumVoices >= 0); | |||
const ScopedLock sl (voicesLock); | |||
while (voices.size() > newNumVoices) | |||
{ | |||
if (auto* voice = findFreeVoice ({}, true)) | |||
voices.removeObject (voice); | |||
else | |||
voices.remove (0); // if there's no voice to steal, kill the oldest voice | |||
} | |||
} | |||
void MPESynthesiser::turnOffAllVoices (bool allowTailOff) | |||
{ | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
// first turn off all voices (it's more efficient to do this immediately | |||
// rather than to go through the MPEInstrument for this). | |||
for (auto* voice : voices) | |||
{ | |||
voice->currentlyPlayingNote.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number | |||
voice->currentlyPlayingNote.keyState = MPENote::off; | |||
voice->noteStopped (allowTailOff); | |||
} | |||
} | |||
// finally make sure the MPE Instrument also doesn't have any notes anymore. | |||
instrument.releaseAllNotes(); | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isActive()) | |||
voice->renderNextBlock (buffer, startSample, numSamples); | |||
} | |||
} | |||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (auto* voice : voices) | |||
{ | |||
if (voice->isActive()) | |||
voice->renderNextBlock (buffer, startSample, numSamples); | |||
} | |||
} | |||
} // namespace juce |
@@ -1,311 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Base class for an MPE-compatible musical device that can play sounds. | |||
This class extends MPESynthesiserBase by adding the concept of voices, | |||
each of which can play a sound triggered by a MPENote that can be modulated | |||
by MPE dimensions like pressure, pitchbend, and timbre, while the note is | |||
sounding. | |||
To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice | |||
which can play back one of these sounds at a time. | |||
Then you can use the addVoice() methods to give the synthesiser a set of voices | |||
it can use to play notes. If you only give it one voice it will be monophonic - | |||
the more voices it has, the more polyphony it'll have available. | |||
Then repeatedly call the renderNextBlock() method to produce the audio (inherited | |||
from MPESynthesiserBase). The voices will be started, stopped, and modulated | |||
automatically, based on the MPE/MIDI messages that the synthesiser receives. | |||
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||
what the target playback rate is. This value is passed on to the voices so that | |||
they can pitch their output correctly. | |||
@see MPESynthesiserBase, MPESynthesiserVoice, MPENote, MPEInstrument | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API MPESynthesiser : public MPESynthesiserBase | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Constructor. | |||
You'll need to add some voices before it'll make any sound. | |||
@see addVoice | |||
*/ | |||
MPESynthesiser(); | |||
/** Constructor to pass to the synthesiser a custom MPEInstrument object | |||
to handle the MPE note state, MIDI channel assignment etc. | |||
(in case you need custom logic for this that goes beyond MIDI and MPE). | |||
@see MPESynthesiserBase, MPEInstrument | |||
*/ | |||
MPESynthesiser (MPEInstrument& instrumentToUse); | |||
/** Destructor. */ | |||
~MPESynthesiser() override; | |||
//============================================================================== | |||
/** Deletes all voices. */ | |||
void clearVoices(); | |||
/** Returns the number of voices that have been added. */ | |||
int getNumVoices() const noexcept { return voices.size(); } | |||
/** Returns one of the voices that have been added. */ | |||
MPESynthesiserVoice* getVoice (int index) const; | |||
/** Adds a new voice to the synth. | |||
All the voices should be the same class of object and are treated equally. | |||
The object passed in will be managed by the synthesiser, which will delete | |||
it later on when no longer needed. The caller should not retain a pointer to the | |||
voice. | |||
*/ | |||
void addVoice (MPESynthesiserVoice* newVoice); | |||
/** Deletes one of the voices. */ | |||
void removeVoice (int index); | |||
/** Reduces the number of voices to newNumVoices. | |||
This will repeatedly call findVoiceToSteal() and remove that voice, until | |||
the total number of voices equals newNumVoices. If newNumVoices is greater than | |||
or equal to the current number of voices, this method does nothing. | |||
*/ | |||
void reduceNumVoices (int newNumVoices); | |||
/** Release all MPE notes and turn off all voices. | |||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||
(if they can do). If this is false, the notes will all be cut off immediately. | |||
This method is meant to be called by the user, for example to implement | |||
a MIDI panic button in a synth. | |||
*/ | |||
virtual void turnOffAllVoices (bool allowTailOff); | |||
//============================================================================== | |||
/** If set to true, then the synth will try to take over an existing voice if | |||
it runs out and needs to play another note. | |||
The value of this boolean is passed into findFreeVoice(), so the result will | |||
depend on the implementation of this method. | |||
*/ | |||
void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } | |||
/** Returns true if note-stealing is enabled. */ | |||
bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } | |||
//============================================================================== | |||
/** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||
This overrides the implementation in MPESynthesiserBase, to additionally | |||
propagate the new value to the voices so that they can use it to render the correct | |||
pitches. | |||
*/ | |||
void setCurrentPlaybackSampleRate (double newRate) override; | |||
//============================================================================== | |||
/** Handle incoming MIDI events. | |||
This method will be called automatically according to the MIDI data passed | |||
into renderNextBlock(), but you can also call it yourself to manually | |||
inject MIDI events. | |||
This implementation forwards program change messages and non-MPE-related | |||
controller messages to handleProgramChange and handleController, respectively, | |||
and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal | |||
with MPE-related MIDI messages used for MPE notes, zones etc. | |||
This method can be overridden further if you need to do custom MIDI | |||
handling on top of what is provided here. | |||
*/ | |||
void handleMidiEvent (const MidiMessage&) override; | |||
/** Callback for MIDI controller messages. The default implementation | |||
provided here does nothing; override this method if you need custom | |||
MIDI controller handling on top of MPE. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). | |||
*/ | |||
virtual void handleController (int /*midiChannel*/, | |||
int /*controllerNumber*/, | |||
int /*controllerValue*/) {} | |||
/** Callback for MIDI program change messages. The default implementation | |||
provided here does nothing; override this method if you need to handle | |||
those messages. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). | |||
*/ | |||
virtual void handleProgramChange (int /*midiChannel*/, | |||
int /*programNumber*/) {} | |||
protected: | |||
//============================================================================== | |||
/** Attempts to start playing a new note. | |||
The default method here will find a free voice that is appropriate for | |||
playing the given MPENote, and use that voice to start playing the sound. | |||
If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), | |||
the synthesiser will use the voice stealing algorithm to find a free voice for | |||
the note (if no voices are free otherwise). | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
will become inconsistent. | |||
*/ | |||
void noteAdded (MPENote newNote) override; | |||
/** Stops playing a note. | |||
This will be called whenever an MPE note is released (either by a note-off message, | |||
or by a sustain/sostenuto pedal release for a note that already received a note-off), | |||
and should therefore stop playing. | |||
This will find any voice that is currently playing finishedNote, | |||
turn its currently playing note off, and call its noteStopped callback. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
will become inconsistent. | |||
*/ | |||
void noteReleased (MPENote finishedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its notePressureChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
void notePressureChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its notePitchbendChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
void notePitchbendChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its noteTimbreChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
void noteTimbreChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its noteKeyStateChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
void noteKeyStateChanged (MPENote changedNote) override; | |||
//============================================================================== | |||
/** This will simply call renderNextBlock for each currently active | |||
voice and fill the buffer with the sum. | |||
Override this method if you need to do more work to render your audio. | |||
*/ | |||
void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||
int startSample, | |||
int numSamples) override; | |||
/** This will simply call renderNextBlock for each currently active | |||
voice and fill the buffer with the sum. (double-precision version) | |||
Override this method if you need to do more work to render your audio. | |||
*/ | |||
void renderNextSubBlock (AudioBuffer<double>& outputAudio, | |||
int startSample, | |||
int numSamples) override; | |||
//============================================================================== | |||
/** Searches through the voices to find one that's not currently playing, and | |||
which can play the given MPE note. | |||
If all voices are active and stealIfNoneAvailable is false, this returns | |||
a nullptr. If all voices are active and stealIfNoneAvailable is true, | |||
this will call findVoiceToSteal() to find a voice. | |||
If you need to find a free voice for something else than playing a note | |||
(e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. | |||
*/ | |||
virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, | |||
bool stealIfNoneAvailable) const; | |||
/** Chooses a voice that is most suitable for being re-used to play a new | |||
note, or for being deleted by reduceNumVoices. | |||
The default method will attempt to find the oldest voice that isn't the | |||
bottom or top note being played. If that's not suitable for your synth, | |||
you can override this method and do something more cunning instead. | |||
If you pass a valid MPENote for the optional argument, then the note number | |||
of that note will be taken into account for finding the ideal voice to steal. | |||
If you pass an invalid (default-constructed) MPENote instead, this part of | |||
the algorithm will be ignored. | |||
*/ | |||
virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; | |||
/** Starts a specified voice and tells it to play a particular MPENote. | |||
You should never need to call this, it's called internally by | |||
MPESynthesiserBase::instrument via the noteStarted callback, | |||
but is protected in case it's useful for some custom subclasses. | |||
*/ | |||
void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); | |||
/** Stops a given voice and tells it to stop playing a particular MPENote | |||
(which should be the same note it is actually playing). | |||
You should never need to call this, it's called internally by | |||
MPESynthesiserBase::instrument via the noteReleased callback, | |||
but is protected in case it's useful for some custom subclasses. | |||
*/ | |||
void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); | |||
//============================================================================== | |||
OwnedArray<MPESynthesiserVoice> voices; | |||
CriticalSection voicesLock; | |||
private: | |||
//============================================================================== | |||
std::atomic<bool> shouldStealVoices { false }; | |||
uint32 lastNoteOnCounter = 0; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||
}; | |||
} // namespace juce |
@@ -1,375 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MPESynthesiserBase::MPESynthesiserBase() | |||
: instrument (defaultInstrument) | |||
{ | |||
instrument.addListener (this); | |||
} | |||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument& inst) | |||
: instrument (inst) | |||
{ | |||
instrument.addListener (this); | |||
} | |||
//============================================================================== | |||
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept | |||
{ | |||
return instrument.getZoneLayout(); | |||
} | |||
void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout) | |||
{ | |||
instrument.setZoneLayout (newLayout); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange) | |||
{ | |||
instrument.enableLegacyMode (pitchbendRange, channelRange); | |||
} | |||
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept | |||
{ | |||
return instrument.isLegacyModeEnabled(); | |||
} | |||
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept | |||
{ | |||
return instrument.getLegacyModeChannelRange(); | |||
} | |||
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange) | |||
{ | |||
instrument.setLegacyModeChannelRange (channelRange); | |||
} | |||
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept | |||
{ | |||
return instrument.getLegacyModePitchbendRange(); | |||
} | |||
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange) | |||
{ | |||
instrument.setLegacyModePitchbendRange (pitchbendRange); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument.setPressureTrackingMode (modeToUse); | |||
} | |||
void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument.setPitchbendTrackingMode (modeToUse); | |||
} | |||
void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument.setTimbreTrackingMode (modeToUse); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m) | |||
{ | |||
instrument.processNextMidiEvent (m); | |||
} | |||
//============================================================================== | |||
template <typename floatType> | |||
void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples) | |||
{ | |||
// you must set the sample rate before using this! | |||
jassert (sampleRate != 0); | |||
const ScopedLock sl (noteStateLock); | |||
auto prevSample = startSample; | |||
const auto endSample = startSample + numSamples; | |||
for (auto it = inputMidi.findNextSamplePosition (startSample); it != inputMidi.cend(); ++it) | |||
{ | |||
const auto metadata = *it; | |||
if (metadata.samplePosition >= endSample) | |||
break; | |||
const auto smallBlockAllowed = (prevSample == startSample && ! subBlockSubdivisionIsStrict); | |||
const auto thisBlockSize = smallBlockAllowed ? 1 : minimumSubBlockSize; | |||
if (metadata.samplePosition >= prevSample + thisBlockSize) | |||
{ | |||
renderNextSubBlock (outputAudio, prevSample, metadata.samplePosition - prevSample); | |||
prevSample = metadata.samplePosition; | |||
} | |||
handleMidiEvent (metadata.getMessage()); | |||
} | |||
if (prevSample < endSample) | |||
renderNextSubBlock (outputAudio, prevSample, endSample - prevSample); | |||
} | |||
// explicit instantiation for supported float types: | |||
template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int); | |||
template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int); | |||
//============================================================================== | |||
void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
{ | |||
if (sampleRate != newRate) | |||
{ | |||
const ScopedLock sl (noteStateLock); | |||
instrument.releaseAllNotes(); | |||
sampleRate = newRate; | |||
} | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||
{ | |||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||
minimumSubBlockSize = numSamples; | |||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||
} | |||
#if JUCE_UNIT_TESTS | |||
namespace | |||
{ | |||
class MpeSynthesiserBaseTests : public UnitTest | |||
{ | |||
enum class CallbackKind { process, midi }; | |||
struct StartAndLength | |||
{ | |||
StartAndLength (int s, int l) : start (s), length (l) {} | |||
int start = 0; | |||
int length = 0; | |||
std::tuple<const int&, const int&> tie() const noexcept { return std::tie (start, length); } | |||
bool operator== (const StartAndLength& other) const noexcept { return tie() == other.tie(); } | |||
bool operator!= (const StartAndLength& other) const noexcept { return tie() != other.tie(); } | |||
bool operator< (const StartAndLength& other) const noexcept { return tie() < other.tie(); } | |||
}; | |||
struct Events | |||
{ | |||
std::vector<StartAndLength> blocks; | |||
std::vector<MidiMessage> messages; | |||
std::vector<CallbackKind> order; | |||
}; | |||
class MockSynthesiser : public MPESynthesiserBase | |||
{ | |||
public: | |||
Events events; | |||
void handleMidiEvent (const MidiMessage& m) override | |||
{ | |||
events.messages.emplace_back (m); | |||
events.order.emplace_back (CallbackKind::midi); | |||
} | |||
private: | |||
using MPESynthesiserBase::renderNextSubBlock; | |||
void renderNextSubBlock (AudioBuffer<float>&, | |||
int startSample, | |||
int numSamples) override | |||
{ | |||
events.blocks.push_back ({ startSample, numSamples }); | |||
events.order.emplace_back (CallbackKind::process); | |||
} | |||
}; | |||
static MidiBuffer makeTestBuffer (const int bufferLength) | |||
{ | |||
MidiBuffer result; | |||
for (int i = 0; i != bufferLength; ++i) | |||
result.addEvent ({}, i); | |||
return result; | |||
} | |||
public: | |||
MpeSynthesiserBaseTests() | |||
: UnitTest ("MPE Synthesiser Base", UnitTestCategories::midi) {} | |||
void runTest() override | |||
{ | |||
const auto sumBlockLengths = [] (const std::vector<StartAndLength>& b) | |||
{ | |||
const auto addBlock = [] (int acc, const StartAndLength& info) { return acc + info.length; }; | |||
return std::accumulate (b.begin(), b.end(), 0, addBlock); | |||
}; | |||
beginTest ("Rendering sparse subblocks works"); | |||
{ | |||
const int blockSize = 512; | |||
const auto midi = [&] { MidiBuffer b; b.addEvent ({}, blockSize / 2); return b; }(); | |||
AudioBuffer<float> audio (1, blockSize); | |||
const auto processEvents = [&] (int start, int length) | |||
{ | |||
MockSynthesiser synth; | |||
synth.setMinimumRenderingSubdivisionSize (1, false); | |||
synth.setCurrentPlaybackSampleRate (44100); | |||
synth.renderNextBlock (audio, midi, start, length); | |||
return synth.events; | |||
}; | |||
{ | |||
const auto e = processEvents (0, blockSize); | |||
expect (e.blocks.size() == 2); | |||
expect (e.messages.size() == 1); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == blockSize); | |||
expect (e.order == std::vector<CallbackKind> { CallbackKind::process, | |||
CallbackKind::midi, | |||
CallbackKind::process }); | |||
} | |||
} | |||
beginTest ("Rendering subblocks processes only contained midi events"); | |||
{ | |||
const int blockSize = 512; | |||
const auto midi = makeTestBuffer (blockSize); | |||
AudioBuffer<float> audio (1, blockSize); | |||
const auto processEvents = [&] (int start, int length) | |||
{ | |||
MockSynthesiser synth; | |||
synth.setMinimumRenderingSubdivisionSize (1, false); | |||
synth.setCurrentPlaybackSampleRate (44100); | |||
synth.renderNextBlock (audio, midi, start, length); | |||
return synth.events; | |||
}; | |||
{ | |||
const int subBlockLength = 0; | |||
const auto e = processEvents (0, subBlockLength); | |||
expect (e.blocks.size() == 0); | |||
expect (e.messages.size() == 0); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||
} | |||
{ | |||
const int subBlockLength = 0; | |||
const auto e = processEvents (1, subBlockLength); | |||
expect (e.blocks.size() == 0); | |||
expect (e.messages.size() == 0); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||
} | |||
{ | |||
const int subBlockLength = 1; | |||
const auto e = processEvents (1, subBlockLength); | |||
expect (e.blocks.size() == 1); | |||
expect (e.messages.size() == 1); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == subBlockLength); | |||
expect (e.order == std::vector<CallbackKind> { CallbackKind::midi, | |||
CallbackKind::process }); | |||
} | |||
{ | |||
const auto e = processEvents (0, blockSize); | |||
expect (e.blocks.size() == blockSize); | |||
expect (e.messages.size() == blockSize); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == blockSize); | |||
expect (e.order.front() == CallbackKind::midi); | |||
} | |||
} | |||
beginTest ("Subblocks respect their minimum size"); | |||
{ | |||
const int blockSize = 512; | |||
const auto midi = makeTestBuffer (blockSize); | |||
AudioBuffer<float> audio (1, blockSize); | |||
const auto blockLengthsAreValid = [] (const std::vector<StartAndLength>& info, int minLength, bool strict) | |||
{ | |||
if (info.size() <= 1) | |||
return true; | |||
const auto lengthIsValid = [&] (const StartAndLength& s) { return minLength <= s.length; }; | |||
const auto begin = strict ? info.begin() : std::next (info.begin()); | |||
// The final block is allowed to be shorter than the minLength | |||
return std::all_of (begin, std::prev (info.end()), lengthIsValid); | |||
}; | |||
for (auto strict : { false, true }) | |||
{ | |||
for (auto subblockSize : { 1, 16, 32, 64, 1024 }) | |||
{ | |||
MockSynthesiser synth; | |||
synth.setMinimumRenderingSubdivisionSize (subblockSize, strict); | |||
synth.setCurrentPlaybackSampleRate (44100); | |||
synth.renderNextBlock (audio, midi, 0, blockSize); | |||
const auto& e = synth.events; | |||
expectWithinAbsoluteError (float (e.blocks.size()), | |||
std::ceil ((float) blockSize / (float) subblockSize), | |||
1.0f); | |||
expect (e.messages.size() == blockSize); | |||
expect (std::is_sorted (e.blocks.begin(), e.blocks.end())); | |||
expect (sumBlockLengths (e.blocks) == blockSize); | |||
expect (blockLengthsAreValid (e.blocks, subblockSize, strict)); | |||
} | |||
} | |||
{ | |||
MockSynthesiser synth; | |||
synth.setMinimumRenderingSubdivisionSize (32, true); | |||
synth.setCurrentPlaybackSampleRate (44100); | |||
synth.renderNextBlock (audio, MidiBuffer{}, 0, 16); | |||
expect (synth.events.blocks == std::vector<StartAndLength> { { 0, 16 } }); | |||
expect (synth.events.order == std::vector<CallbackKind> { CallbackKind::process }); | |||
expect (synth.events.messages.empty()); | |||
} | |||
} | |||
} | |||
}; | |||
MpeSynthesiserBaseTests mpeSynthesiserBaseTests; | |||
} | |||
#endif | |||
} // namespace juce |
@@ -1,216 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
Derive from this class to create a basic audio generator capable of MPE. | |||
Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged | |||
etc.) to let your audio generator know that MPE notes were triggered, modulated, | |||
or released. What to do inside them, and how that influences your audio generator, | |||
is up to you! | |||
This class uses an instance of MPEInstrument internally to handle the MPE | |||
note state logic. | |||
This class is a very low-level base class for an MPE instrument. If you need | |||
something more sophisticated, have a look at MPESynthesiser. This class extends | |||
MPESynthesiserBase by adding the concept of voices that can play notes, | |||
a voice stealing algorithm, and much more. | |||
@see MPESynthesiser, MPEInstrument | |||
@tags{Audio} | |||
*/ | |||
struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Constructor. */ | |||
MPESynthesiserBase(); | |||
/** Constructor. | |||
If you use this constructor, the synthesiser will use the provided instrument | |||
object to handle the MPE note state logic. | |||
This is useful if you want to use an instance of your own class derived | |||
from MPEInstrument for the MPE logic. | |||
*/ | |||
MPESynthesiserBase (MPEInstrument& instrument); | |||
//============================================================================== | |||
/** Returns the synthesiser's internal MPE zone layout. | |||
This happens by value, to enforce thread-safety and class invariants. | |||
*/ | |||
MPEZoneLayout getZoneLayout() const noexcept; | |||
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in. | |||
As a side effect, this will discard all currently playing notes, | |||
call noteReleased for all of them, and disable legacy mode (if previously enabled). | |||
*/ | |||
void setZoneLayout (MPEZoneLayout newLayout); | |||
//============================================================================== | |||
/** Tells the synthesiser what the sample rate is for the audio it's being | |||
used to render. | |||
*/ | |||
virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||
/** Returns the current target sample rate at which rendering is being done. | |||
Subclasses may need to know this so that they can pitch things correctly. | |||
*/ | |||
double getSampleRate() const noexcept { return sampleRate; } | |||
//============================================================================== | |||
/** Creates the next block of audio output. | |||
Call this to make sound. This will chop up the AudioBuffer into subBlock | |||
pieces separated by events in the MIDI buffer, and then call | |||
renderNextSubBlock on each one of them. In between you will get calls | |||
to noteAdded/Changed/Finished, where you can update parameters that | |||
depend on those notes to use for your audio rendering. | |||
@param outputAudio Buffer into which audio will be rendered | |||
@param inputMidi MIDI events to process | |||
@param startSample The first sample to process in both buffers | |||
@param numSamples The number of samples to process | |||
*/ | |||
template <typename floatType> | |||
void renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples); | |||
//============================================================================== | |||
/** Handle incoming MIDI events (called from renderNextBlock). | |||
The default implementation provided here simply forwards everything | |||
to MPEInstrument::processNextMidiEvent, where it is used to update the | |||
MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. | |||
This method can be overridden if you need to do custom MIDI handling | |||
on top of MPE. The MPESynthesiser class overrides this to implement | |||
callbacks for MIDI program changes and non-MPE-related MIDI controller | |||
messages. | |||
*/ | |||
virtual void handleMidiEvent (const MidiMessage&); | |||
//============================================================================== | |||
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||
When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||
into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||
sub-blocks that are rendered with multiple calls to renderVoices(). | |||
Obviously in a pathological case where there are midi messages on every sample, then | |||
renderVoices() could be called once per sample and lead to poor performance, so this | |||
setting allows you to set a lower limit on the block size. | |||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||
decrease this value for your synth. | |||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||
(this can sometimes help to avoid quantisation or phasing issues). | |||
*/ | |||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||
//============================================================================== | |||
/** Puts the synthesiser into legacy mode. | |||
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||
Must be between 0 and 96, otherwise behaviour is undefined. | |||
The default pitchbend range in legacy mode is +/- 2 semitones. | |||
@param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||
The default is to use all MIDI channels (1-16). | |||
To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||
*/ | |||
void enableLegacyMode (int pitchbendRange = 2, | |||
Range<int> channelRange = Range<int> (1, 17)); | |||
/** Returns true if the instrument is in legacy mode, false otherwise. */ | |||
bool isLegacyModeEnabled() const noexcept; | |||
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
Range<int> getLegacyModeChannelRange() const noexcept; | |||
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
void setLegacyModeChannelRange (Range<int> channelRange); | |||
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
int getLegacyModePitchbendRange() const noexcept; | |||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
void setLegacyModePitchbendRange (int pitchbendRange); | |||
//============================================================================== | |||
using TrackingMode = MPEInstrument::TrackingMode; | |||
/** Set the MPE tracking mode for the pressure dimension. */ | |||
void setPressureTrackingMode (TrackingMode modeToUse); | |||
/** Set the MPE tracking mode for the pitchbend dimension. */ | |||
void setPitchbendTrackingMode (TrackingMode modeToUse); | |||
/** Set the MPE tracking mode for the timbre dimension. */ | |||
void setTimbreTrackingMode (TrackingMode modeToUse); | |||
protected: | |||
//============================================================================== | |||
/** Implement this method to render your audio inside. | |||
@see renderNextBlock | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||
int startSample, | |||
int numSamples) = 0; | |||
/** Implement this method if you want to render 64-bit audio as well; | |||
otherwise leave blank. | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<double>& /*outputAudio*/, | |||
int /*startSample*/, | |||
int /*numSamples*/) {} | |||
protected: | |||
//============================================================================== | |||
/** @internal */ | |||
MPEInstrument& instrument; | |||
private: | |||
//============================================================================== | |||
MPEInstrument defaultInstrument { MPEZone (MPEZone::Type::lower, 15) }; | |||
CriticalSection noteStateLock; | |||
double sampleRate = 0.0; | |||
int minimumSubBlockSize = 32; | |||
bool subBlockSubdivisionIsStrict = false; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||
}; | |||
} // namespace juce |
@@ -1,50 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
MPESynthesiserVoice::MPESynthesiserVoice() | |||
{ | |||
} | |||
MPESynthesiserVoice::~MPESynthesiserVoice() | |||
{ | |||
} | |||
//============================================================================== | |||
bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept | |||
{ | |||
return isActive() && currentlyPlayingNote.noteID == note.noteID; | |||
} | |||
bool MPESynthesiserVoice::isPlayingButReleased() const noexcept | |||
{ | |||
return isActive() && currentlyPlayingNote.keyState == MPENote::off; | |||
} | |||
void MPESynthesiserVoice::clearCurrentNote() noexcept | |||
{ | |||
currentlyPlayingNote = MPENote(); | |||
} | |||
} // namespace juce |