Browse Source

Remove juce completely, cleanup

Signed-off-by: falkTX <falktx@falktx.com>
pull/1898/head
falkTX 6 months ago
parent
commit
6f61b89d2d
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
100 changed files with 60 additions and 27021 deletions
  1. +0
    -15
      Makefile
  2. +6
    -22
      Makefile.print.mk
  3. +6
    -54
      source/Makefile.deps.mk
  4. +0
    -12
      source/Makefile.mk
  5. +7
    -26
      source/backend/CarlaEngine.hpp
  6. +2
    -24
      source/backend/CarlaEngineInit.hpp
  7. +2
    -21
      source/backend/CarlaHost.h
  8. +2
    -17
      source/backend/CarlaPlugin.hpp
  9. +2
    -35
      source/backend/CarlaStandalone.cpp
  10. +0
    -23
      source/backend/CarlaUtils.h
  11. +0
    -29
      source/backend/Makefile
  12. +0
    -7
      source/backend/Makefile.mk
  13. +2
    -80
      source/backend/engine/CarlaEngine.cpp
  14. +3
    -31
      source/backend/engine/CarlaEngineJack.cpp
  15. +0
    -1142
      source/backend/engine/CarlaEngineJuce.cpp
  16. +6
    -96
      source/backend/engine/CarlaEngineNative.cpp
  17. +4
    -1
      source/backend/engine/CarlaEngineRtAudio.cpp
  18. +0
    -19
      source/backend/engine/Makefile
  19. +2
    -20
      source/backend/plugin/CarlaPluginAU.cpp
  20. +0
    -1946
      source/backend/plugin/CarlaPluginJuce.cpp
  21. +2
    -26
      source/backend/plugin/CarlaPluginVST2.cpp
  22. +2
    -26
      source/backend/plugin/CarlaPluginVST3.cpp
  23. +0
    -22
      source/backend/plugin/Makefile
  24. +2
    -27
      source/backend/utils/Information.cpp
  25. +0
    -33
      source/backend/utils/JUCE.cpp
  26. +0
    -4
      source/backend/utils/Makefile
  27. +1
    -12
      source/bridges-plugin/CarlaBridgeSingleLV2.cpp
  28. +0
    -36
      source/bridges-plugin/Makefile
  29. +1
    -1
      source/carla.kdev4
  30. +0
    -3
      source/frontend/CarlaFrontend.h
  31. +4
    -2
      source/frontend/Makefile
  32. +1
    -7
      source/frontend/carla_frontend.py
  33. +0
    -10
      source/frontend/carla_host.py
  34. +3
    -10
      source/frontend/carla_widgets.py
  35. +0
    -97
      source/frontend/dialogs/aboutjucedialog.cpp
  36. +0
    -55
      source/frontend/dialogs/aboutjucedialog.hpp
  37. +0
    -77
      source/frontend/dialogs/aboutjucedialog.py
  38. +0
    -187
      source/frontend/dialogs/aboutjucedialog.ui
  39. +0
    -495
      source/modules/AppConfig.h
  40. +0
    -13
      source/modules/Makefile
  41. +0
    -61
      source/modules/carla_juce/Makefile
  42. +0
    -184
      source/modules/carla_juce/carla_juce.cpp
  43. +0
    -70
      source/modules/carla_juce/carla_juce.h
  44. +0
    -70
      source/modules/juce_audio_basics/Makefile
  45. +0
    -593
      source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h
  46. +0
    -741
      source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp
  47. +0
    -525
      source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
  48. +0
    -635
      source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp
  49. +0
    -857
      source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h
  50. +0
    -99
      source/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp
  51. +0
    -109
      source/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h
  52. +0
    -1279
      source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
  53. +0
    -1712
      source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
  54. +0
    -269
      source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h
  55. +0
    -99
      source/modules/juce_audio_basics/juce_audio_basics.cpp
  56. +0
    -123
      source/modules/juce_audio_basics/juce_audio_basics.h
  57. +0
    -320
      source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
  58. +0
    -345
      source/modules/juce_audio_basics/midi/juce_MidiBuffer.h
  59. +0
    -188
      source/modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h
  60. +0
    -794
      source/modules/juce_audio_basics/midi/juce_MidiFile.cpp
  61. +0
    -198
      source/modules/juce_audio_basics/midi/juce_MidiFile.h
  62. +0
    -173
      source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
  63. +0
    -195
      source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
  64. +0
    -1331
      source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
  65. +0
    -986
      source/modules/juce_audio_basics/midi/juce_MidiMessage.h
  66. +0
    -870
      source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
  67. +0
    -315
      source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
  68. +0
    -375
      source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp
  69. +0
    -153
      source/modules/juce_audio_basics/midi/juce_MidiRPN.h
  70. +0
    -47
      source/modules/juce_audio_basics/midi/ump/juce_UMP.h
  71. +0
    -330
      source/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h
  72. +0
    -169
      source/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h
  73. +0
    -202
      source/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h
  74. +0
    -538
      source/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h
  75. +0
    -130
      source/modules/juce_audio_basics/midi/ump/juce_UMPIterator.h
  76. +0
    -217
      source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h
  77. +0
    -195
      source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp
  78. +0
    -191
      source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h
  79. +0
    -48
      source/modules/juce_audio_basics/midi/ump/juce_UMPProtocols.h
  80. +0
    -46
      source/modules/juce_audio_basics/midi/ump/juce_UMPReceiver.h
  81. +0
    -53
      source/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp
  82. +0
    -77
      source/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h
  83. +0
    -59
      source/modules/juce_audio_basics/midi/ump/juce_UMPUtils.cpp
  84. +0
    -117
      source/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h
  85. +0
    -35
      source/modules/juce_audio_basics/midi/ump/juce_UMPView.cpp
  86. +0
    -92
      source/modules/juce_audio_basics/midi/ump/juce_UMPView.h
  87. +0
    -1018
      source/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp
  88. +0
    -193
      source/modules/juce_audio_basics/midi/ump/juce_UMPacket.h
  89. +0
    -96
      source/modules/juce_audio_basics/midi/ump/juce_UMPackets.h
  90. +0
    -2352
      source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp
  91. +0
    -426
      source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h
  92. +0
    -238
      source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp
  93. +0
    -116
      source/modules/juce_audio_basics/mpe/juce_MPEMessages.h
  94. +0
    -127
      source/modules/juce_audio_basics/mpe/juce_MPENote.cpp
  95. +0
    -184
      source/modules/juce_audio_basics/mpe/juce_MPENote.h
  96. +0
    -341
      source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp
  97. +0
    -311
      source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h
  98. +0
    -375
      source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp
  99. +0
    -216
      source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h
  100. +0
    -50
      source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp

+ 0
- 15
Makefile View File

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


+ 6
- 22
Makefile.print.mk View File

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


+ 6
- 54
source/Makefile.deps.mk View File

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


+ 0
- 12
source/Makefile.mk View File

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


+ 7
- 26
source/backend/CarlaEngine.hpp View File

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

/*!


+ 2
- 24
source/backend/CarlaEngineInit.hpp View File

@@ -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();


+ 2
- 21
source/backend/CarlaHost.h View File

@@ -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().
*/


+ 2
- 17
source/backend/CarlaPlugin.hpp View 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);



+ 2
- 35
source/backend/CarlaStandalone.cpp View File

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


+ 0
- 23
source/backend/CarlaUtils.h View File

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



+ 0
- 29
source/backend/Makefile View File

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


+ 0
- 7
source/backend/Makefile.mk View File

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


+ 2
- 80
source/backend/engine/CarlaEngine.cpp View 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

/* 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


+ 3
- 31
source/backend/engine/CarlaEngineJack.cpp View File

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

// -----------------------------------------------------------------------


+ 0
- 1142
source/backend/engine/CarlaEngineJuce.cpp
File diff suppressed because it is too large
View File


+ 6
- 96
source/backend/engine/CarlaEngineNative.cpp View 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

#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; }


+ 4
- 1
source/backend/engine/CarlaEngineRtAudio.cpp View File

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


+ 0
- 19
source/backend/engine/Makefile View File

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


+ 2
- 20
source/backend/plugin/CarlaPluginAU.cpp View File

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

// -------------------------------------------------------------------------------------------------------------------


+ 0
- 1946
source/backend/plugin/CarlaPluginJuce.cpp
File diff suppressed because it is too large
View File


+ 2
- 26
source/backend/plugin/CarlaPluginVST2.cpp View File

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


+ 2
- 26
source/backend/plugin/CarlaPluginVST3.cpp View File

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


+ 0
- 22
source/backend/plugin/Makefile View File

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


+ 2
- 27
source/backend/utils/Information.cpp View File

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



+ 0
- 33
source/backend/utils/JUCE.cpp View File

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

// -------------------------------------------------------------------------------------------------------------------

+ 0
- 4
source/backend/utils/Makefile View File

@@ -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
- 12
source/bridges-plugin/CarlaBridgeSingleLV2.cpp View File

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


+ 0
- 36
source/bridges-plugin/Makefile View File

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


+ 1
- 1
source/carla.kdev4 View File

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

+ 0
- 3
source/frontend/CarlaFrontend.h View File

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



+ 4
- 2
source/frontend/Makefile View File

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


+ 1
- 7
source/frontend/carla_frontend.py View File

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


+ 0
- 10
source/frontend/carla_host.py View File

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


+ 3
- 10
source/frontend/carla_widgets.py View File

@@ -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&#37; 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&#37; complete (missing vst bank/presets and some minor stuff)"))
self.ui.l_vst3.setText(self.tr("About 66&#37; complete"))

if MACOS:
self.ui.l_au.setText(self.tr("Using JUCE host"))
self.ui.l_au.setText(self.tr("About 20&#37; complete"))
else:
self.ui.line_vst3.hide()
self.ui.l_au.hide()


+ 0
- 97
source/frontend/dialogs/aboutjucedialog.cpp View File

@@ -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();
}

// --------------------------------------------------------------------------------------------------------------------

+ 0
- 55
source/frontend/dialogs/aboutjucedialog.hpp View File

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

// --------------------------------------------------------------------------------------------------------------------

+ 0
- 77
source/frontend/dialogs/aboutjucedialog.py View File

@@ -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_()

# ---------------------------------------------------------------------------------------------------------------------

+ 0
- 187
source/frontend/dialogs/aboutjucedialog.ui View File

@@ -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>&lt;b&gt;About JUCE&lt;/b&gt;</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>

+ 0
- 495
source/modules/AppConfig.h View File

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

+ 0
- 13
source/modules/Makefile View File

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

# ---------------------------------------------------------------------------------------------------------------------

+ 0
- 61
source/modules/carla_juce/Makefile View File

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

# ---------------------------------------------------------------------------------------------------------------------

+ 0
- 184
source/modules/carla_juce/carla_juce.cpp View File

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

+ 0
- 70
source/modules/carla_juce/carla_juce.h View File

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

+ 0
- 70
source/modules/juce_audio_basics/Makefile View File

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

# ---------------------------------------------------------------------------------------------------------------------

+ 0
- 593
source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h View File

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

+ 0
- 741
source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp View File

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

+ 0
- 525
source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h View File

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

+ 0
- 635
source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp View File

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

+ 0
- 857
source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h View File

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

+ 0
- 99
source/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp View File

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

+ 0
- 109
source/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h View File

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

+ 0
- 1279
source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
File diff suppressed because it is too large
View File


+ 0
- 1712
source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
File diff suppressed because it is too large
View File


+ 0
- 269
source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h View File

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

+ 0
- 99
source/modules/juce_audio_basics/juce_audio_basics.cpp View File

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

+ 0
- 123
source/modules/juce_audio_basics/juce_audio_basics.h View File

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

+ 0
- 320
source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp View File

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

+ 0
- 345
source/modules/juce_audio_basics/midi/juce_MidiBuffer.h View File

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

+ 0
- 188
source/modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h View File

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

+ 0
- 794
source/modules/juce_audio_basics/midi/juce_MidiFile.cpp View File

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

+ 0
- 198
source/modules/juce_audio_basics/midi/juce_MidiFile.h View File

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

+ 0
- 173
source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp View File

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

+ 0
- 195
source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h View File

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

+ 0
- 1331
source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
File diff suppressed because it is too large
View File


+ 0
- 986
source/modules/juce_audio_basics/midi/juce_MidiMessage.h View File

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

+ 0
- 870
source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp View File

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

+ 0
- 315
source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h View File

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

+ 0
- 375
source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp View File

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

+ 0
- 153
source/modules/juce_audio_basics/midi/juce_MidiRPN.h View File

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

+ 0
- 47
source/modules/juce_audio_basics/midi/ump/juce_UMP.h View File

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

+ 0
- 330
source/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h View File

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

+ 0
- 169
source/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h View File

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

+ 0
- 202
source/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h View File

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

+ 0
- 538
source/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h View File

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

+ 0
- 130
source/modules/juce_audio_basics/midi/ump/juce_UMPIterator.h View File

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

+ 0
- 217
source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h View File

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

+ 0
- 195
source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp View File

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

+ 0
- 191
source/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h View File

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

+ 0
- 48
source/modules/juce_audio_basics/midi/ump/juce_UMPProtocols.h View File

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

+ 0
- 46
source/modules/juce_audio_basics/midi/ump/juce_UMPReceiver.h View File

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

+ 0
- 53
source/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp View File

@@ -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)
};
}
}
}

+ 0
- 77
source/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h View File

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

+ 0
- 59
source/modules/juce_audio_basics/midi/ump/juce_UMPUtils.cpp View File

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

+ 0
- 117
source/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h View File

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

+ 0
- 35
source/modules/juce_audio_basics/midi/ump/juce_UMPView.cpp View File

@@ -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);
}
}
}

+ 0
- 92
source/modules/juce_audio_basics/midi/ump/juce_UMPView.h View File

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

+ 0
- 1018
source/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp
File diff suppressed because it is too large
View File


+ 0
- 193
source/modules/juce_audio_basics/midi/ump/juce_UMPacket.h View File

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

+ 0
- 96
source/modules/juce_audio_basics/midi/ump/juce_UMPackets.h View File

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

+ 0
- 2352
source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp
File diff suppressed because it is too large
View File


+ 0
- 426
source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h View File

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

+ 0
- 238
source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp View File

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

+ 0
- 116
source/modules/juce_audio_basics/mpe/juce_MPEMessages.h View File

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

+ 0
- 127
source/modules/juce_audio_basics/mpe/juce_MPENote.cpp View File

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

+ 0
- 184
source/modules/juce_audio_basics/mpe/juce_MPENote.h View File

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

+ 0
- 341
source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp View File

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

+ 0
- 311
source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h View File

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

+ 0
- 375
source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp View File

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

+ 0
- 216
source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h View File

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

+ 0
- 50
source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp View File

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

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

Loading…
Cancel
Save