diff --git a/dpf/Makefile.base.mk b/dpf/Makefile.base.mk
index 696cdb6..87ae06b 100644
--- a/dpf/Makefile.base.mk
+++ b/dpf/Makefile.base.mk
@@ -196,7 +196,10 @@ else
# Common linker flags
LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1,--gc-sections
-ifneq ($(WASM),true)
+ifeq ($(WASM),true)
+LINK_OPTS += -O3
+LINK_OPTS += -sAGGRESSIVE_VARIABLE_ELIMINATION=1
+else
LINK_OPTS += -Wl,--as-needed
ifneq ($(SKIP_STRIPPING),true)
LINK_OPTS += -Wl,--strip-all
@@ -257,7 +260,7 @@ LINK_FLAGS = $(LINK_OPTS) $(LDFLAGS)
ifeq ($(WASM),true)
# Special flag for emscripten
-LINK_FLAGS += -sLLD_REPORT_UNDEFINED
+LINK_FLAGS += -sENVIRONMENT=web -sLLD_REPORT_UNDEFINED
else ifneq ($(MACOS),true)
# Not available on MacOS
LINK_FLAGS += -Wl,--no-undefined
@@ -267,6 +270,11 @@ ifeq ($(MACOS_OLD),true)
BUILD_CXX_FLAGS = $(BASE_FLAGS) $(CXXFLAGS) -DHAVE_CPP11_SUPPORT=0
endif
+ifeq ($(WASM_CLIPBOARD),true)
+BUILD_CXX_FLAGS += -DPUGL_WASM_ASYNC_CLIPBOARD
+LINK_FLAGS += -sASYNCIFY -sASYNCIFY_IMPORTS=puglGetAsyncClipboardData
+endif
+
ifeq ($(WASM_EXCEPTIONS),true)
BUILD_CXX_FLAGS += -fexceptions
LINK_FLAGS += -fexceptions
@@ -346,7 +354,13 @@ endif
endif
# backwards compat, always available/enabled
+ifneq ($(FORCE_NATIVE_AUDIO_FALLBACK),true)
+ifeq ($(STATIC_BUILD),true)
+HAVE_JACK = $(shell $(PKG_CONFIG) --exists jack && echo true)
+else
HAVE_JACK = true
+endif
+endif
# ---------------------------------------------------------------------------------------------------------------------
# Set Generic DGL stuff
@@ -410,7 +424,9 @@ else ifeq ($(MACOS),true)
OPENGL_FLAGS = -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations
OPENGL_LIBS = -framework OpenGL
else ifeq ($(WASM),true)
-ifneq ($(USE_GLES2),true)
+ifeq ($(USE_GLES2),true)
+OPENGL_LIBS = -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
+else
ifneq ($(USE_GLES3),true)
OPENGL_LIBS = -sLEGACY_GL_EMULATION -sGL_UNSAFE_OPTS=0
endif
diff --git a/dpf/Makefile.plugins.mk b/dpf/Makefile.plugins.mk
index d419de7..4794a08 100644
--- a/dpf/Makefile.plugins.mk
+++ b/dpf/Makefile.plugins.mk
@@ -81,20 +81,23 @@ endif
else ifneq ($(SKIP_RTAUDIO_FALLBACK),true)
ifeq ($(MACOS),true)
-JACK_LIBS += -framework CoreAudio -framework CoreFoundation
+JACK_LIBS += -framework CoreAudio -framework CoreFoundation -framework CoreMIDI
else ifeq ($(WINDOWS),true)
JACK_LIBS += -lole32 -lwinmm
# DirectSound
JACK_LIBS += -ldsound
# WASAPI
# JACK_LIBS += -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid
-else ifeq ($(HAVE_PULSEAUDIO),true)
+else
+ifeq ($(HAVE_PULSEAUDIO),true)
JACK_FLAGS += $(PULSEAUDIO_FLAGS)
JACK_LIBS += $(PULSEAUDIO_LIBS)
-else ifeq ($(HAVE_ALSA),true)
+endif
+ifeq ($(HAVE_ALSA),true)
JACK_FLAGS += $(ALSA_FLAGS)
JACK_LIBS += $(ALSA_LIBS)
endif
+endif
ifeq ($(HAVE_RTAUDIO),true)
ifneq ($(HAIKU),true)
@@ -119,6 +122,93 @@ ifeq ($(MACOS),true)
OBJS_UI += $(BUILD_DIR)/DistrhoUI_macOS_$(NAME).mm.o
endif
+# ---------------------------------------------------------------------------------------------------------------------
+# Handle UI stuff, disable UI support automatically
+
+ifeq ($(FILES_UI),)
+HAVE_DGL = false
+UI_TYPE = none
+endif
+
+ifeq ($(UI_TYPE),)
+UI_TYPE = opengl
+endif
+
+ifeq ($(UI_TYPE),generic)
+ifeq ($(HAVE_OPENGL),true)
+UI_TYPE = opengl
+else ifeq ($(HAVE_CAIRO),true)
+UI_TYPE = cairo
+endif
+endif
+
+ifeq ($(UI_TYPE),cairo)
+ifeq ($(HAVE_CAIRO),true)
+DGL_FLAGS += -DDGL_CAIRO -DHAVE_DGL
+DGL_FLAGS += $(CAIRO_FLAGS)
+DGL_LIBS += $(CAIRO_LIBS)
+DGL_LIB = $(DPF_PATH)/build/libdgl-cairo.a
+HAVE_DGL = true
+else
+HAVE_DGL = false
+endif
+endif
+
+ifeq ($(UI_TYPE),opengl)
+ifeq ($(HAVE_OPENGL),true)
+DGL_FLAGS += -DDGL_OPENGL -DHAVE_DGL
+DGL_FLAGS += $(OPENGL_FLAGS)
+DGL_LIBS += $(OPENGL_LIBS)
+DGL_LIB = $(DPF_PATH)/build/libdgl-opengl.a
+HAVE_DGL = true
+else
+HAVE_DGL = false
+endif
+endif
+
+ifeq ($(UI_TYPE),opengl3)
+ifeq ($(HAVE_OPENGL),true)
+DGL_FLAGS += -DDGL_OPENGL -DDGL_USE_OPENGL3 -DHAVE_DGL
+DGL_FLAGS += $(OPENGL_FLAGS)
+DGL_LIBS += $(OPENGL_LIBS)
+DGL_LIB = $(DPF_PATH)/build/libdgl-opengl3.a
+HAVE_DGL = true
+else
+HAVE_DGL = false
+endif
+endif
+
+ifeq ($(UI_TYPE),vulkan)
+ifeq ($(HAVE_VULKAN),true)
+DGL_FLAGS += -DDGL_VULKAN -DHAVE_DGL
+DGL_FLAGS += $(VULKAN_FLAGS)
+DGL_LIBS += $(VULKAN_LIBS)
+DGL_LIB = $(DPF_PATH)/build/libdgl-vulkan.a
+HAVE_DGL = true
+else
+HAVE_DGL = false
+endif
+endif
+
+ifeq ($(UI_TYPE),external)
+DGL_FLAGS += -DDGL_EXTERNAL
+HAVE_DGL = true
+endif
+
+ifeq ($(UI_TYPE),stub)
+ifeq ($(HAVE_STUB),true)
+DGL_LIB = $(DPF_PATH)/build/libdgl-stub.a
+HAVE_DGL = true
+else
+HAVE_DGL = false
+endif
+endif
+
+DGL_LIBS += $(DGL_SYSTEM_LIBS) -lm
+
+# TODO split dsp and ui object build flags
+BASE_FLAGS += $(DGL_FLAGS)
+
# ---------------------------------------------------------------------------------------------------------------------
# Set VST2 filename, either single binary or inside a bundle
@@ -152,7 +242,18 @@ endif
# ---------------------------------------------------------------------------------------------------------------------
# Set plugin binary file targets
+ifeq ($(MACOS),true)
+ifeq ($(HAVE_DGL),true)
+MACOS_APP_BUNDLE = true
+endif
+endif
+
+ifeq ($(MACOS_APP_BUNDLE),true)
+jack = $(TARGET_DIR)/$(NAME).app/Contents/MacOS/$(NAME)
+jackfiles = $(TARGET_DIR)/$(NAME).app/Contents/Info.plist
+else
jack = $(TARGET_DIR)/$(NAME)$(APP_EXT)
+endif
ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT)
dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT)
dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_EXT)
@@ -175,6 +276,17 @@ vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/PkgInfo
vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Resources/empty.lproj
endif
+ifneq ($(HAVE_DGL),true)
+dssi_ui =
+lv2_ui =
+DGL_LIBS =
+OBJS_UI =
+endif
+
+ifneq ($(HAVE_LIBLO),true)
+dssi_ui =
+endif
+
# ---------------------------------------------------------------------------------------------------------------------
# Set plugin symbols to export
@@ -216,104 +328,6 @@ SYMBOLS_VST3 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/vst3.version
SYMBOLS_SHARED = -Wl,--version-script=$(DPF_PATH)/utils/symbols/shared.version
endif
-# ---------------------------------------------------------------------------------------------------------------------
-# Handle UI stuff, disable UI support automatically
-
-ifeq ($(FILES_UI),)
-HAVE_DGL = false
-UI_TYPE = none
-endif
-
-ifeq ($(UI_TYPE),)
-UI_TYPE = opengl
-endif
-
-ifeq ($(UI_TYPE),generic)
-ifeq ($(HAVE_OPENGL),true)
-UI_TYPE = opengl
-else ifeq ($(HAVE_CAIRO),true)
-UI_TYPE = cairo
-endif
-endif
-
-ifeq ($(UI_TYPE),cairo)
-ifeq ($(HAVE_CAIRO),true)
-DGL_FLAGS += -DDGL_CAIRO -DHAVE_DGL
-DGL_FLAGS += $(CAIRO_FLAGS)
-DGL_LIBS += $(CAIRO_LIBS)
-DGL_LIB = $(DPF_PATH)/build/libdgl-cairo.a
-HAVE_DGL = true
-else
-HAVE_DGL = false
-endif
-endif
-
-ifeq ($(UI_TYPE),opengl)
-ifeq ($(HAVE_OPENGL),true)
-DGL_FLAGS += -DDGL_OPENGL -DHAVE_DGL
-DGL_FLAGS += $(OPENGL_FLAGS)
-DGL_LIBS += $(OPENGL_LIBS)
-DGL_LIB = $(DPF_PATH)/build/libdgl-opengl.a
-HAVE_DGL = true
-else
-HAVE_DGL = false
-endif
-endif
-
-ifeq ($(UI_TYPE),opengl3)
-ifeq ($(HAVE_OPENGL),true)
-DGL_FLAGS += -DDGL_OPENGL -DDGL_USE_OPENGL3 -DHAVE_DGL
-DGL_FLAGS += $(OPENGL_FLAGS)
-DGL_LIBS += $(OPENGL_LIBS)
-DGL_LIB = $(DPF_PATH)/build/libdgl-opengl3.a
-HAVE_DGL = true
-else
-HAVE_DGL = false
-endif
-endif
-
-ifeq ($(UI_TYPE),vulkan)
-ifeq ($(HAVE_VULKAN),true)
-DGL_FLAGS += -DDGL_VULKAN -DHAVE_DGL
-DGL_FLAGS += $(VULKAN_FLAGS)
-DGL_LIBS += $(VULKAN_LIBS)
-DGL_LIB = $(DPF_PATH)/build/libdgl-vulkan.a
-HAVE_DGL = true
-else
-HAVE_DGL = false
-endif
-endif
-
-ifeq ($(UI_TYPE),external)
-DGL_FLAGS += -DDGL_EXTERNAL
-HAVE_DGL = true
-endif
-
-ifeq ($(UI_TYPE),stub)
-ifeq ($(HAVE_STUB),true)
-DGL_LIB = $(DPF_PATH)/build/libdgl-stub.a
-HAVE_DGL = true
-else
-HAVE_DGL = false
-endif
-endif
-
-DGL_LIBS += $(DGL_SYSTEM_LIBS) -lm
-
-ifneq ($(HAVE_DGL),true)
-dssi_ui =
-lv2_ui =
-DGL_LIBS =
-OBJS_UI =
-endif
-
-ifneq ($(HAVE_LIBLO),true)
-dssi_ui =
-endif
-
-# TODO split dsp and ui object build flags
-BASE_FLAGS += $(DGL_FLAGS)
-
# ---------------------------------------------------------------------------------------------------------------------
# Runtime test build
@@ -415,7 +429,7 @@ $(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(E
# ---------------------------------------------------------------------------------------------------------------------
# JACK
-jack: $(jack)
+jack: $(jack) $(jackfiles)
ifeq ($(HAVE_DGL),true)
$(jack): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.o $(DGL_LIB)
@@ -539,7 +553,15 @@ endif
# ---------------------------------------------------------------------------------------------------------------------
# macOS files
-$(TARGET_DIR)/%/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist
+$(TARGET_DIR)/%.app/Contents/Info.plist: $(DPF_PATH)/utils/plugin.app/Contents/Info.plist
+ -@mkdir -p $(shell dirname $@)
+ $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@
+
+$(TARGET_DIR)/%.vst/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist
+ -@mkdir -p $(shell dirname $@)
+ $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@
+
+$(TARGET_DIR)/%.vst3/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist
-@mkdir -p $(shell dirname $@)
$(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@
diff --git a/dpf/README.md b/dpf/README.md
index 18dbc65..95003a9 100644
--- a/dpf/README.md
+++ b/dpf/README.md
@@ -7,7 +7,7 @@ DPF is designed to make development of new plugins an easy and enjoyable task.
The framework facilitates exporting various different plugin formats from the same code-base.
-DPF can build for LADSPA, DSSI, LV2 and VST formats.
+DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.
All current plugin format implementations are complete.
A JACK/Standalone mode is also available, allowing you to quickly test plugins.
diff --git a/dpf/dgl/src/Application.cpp b/dpf/dgl/src/Application.cpp
index 0ec4de8..5fe05de 100644
--- a/dpf/dgl/src/Application.cpp
+++ b/dpf/dgl/src/Application.cpp
@@ -16,8 +16,10 @@
#include "ApplicationPrivateData.hpp"
-#ifdef __EMSCRIPTEN__
+#if defined(__EMSCRIPTEN__)
# include
+#elif defined(DISTRHO_OS_MAC)
+# include
#endif
START_NAMESPACE_DGL
@@ -48,8 +50,18 @@ void Application::exec(const uint idleTimeInMs)
{
DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,);
-#ifdef __EMSCRIPTEN__
+#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop_arg(app_idle, this, 0, true);
+#elif defined(DISTRHO_OS_MAC)
+ const CFTimeInterval idleTimeInSecs = static_cast(idleTimeInMs) / 1000;
+
+ while (! pData->isQuitting)
+ {
+ pData->idle(0);
+
+ if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, idleTimeInSecs, true) == kCFRunLoopRunFinished)
+ break;
+ }
#else
while (! pData->isQuitting)
pData->idle(idleTimeInMs);
diff --git a/dpf/dgl/src/Cairo.cpp b/dpf/dgl/src/Cairo.cpp
index 6cd7f20..1a5b69e 100644
--- a/dpf/dgl/src/Cairo.cpp
+++ b/dpf/dgl/src/Cairo.cpp
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho
+ * Copyright (C) 2012-2022 Filipe Coelho
* Copyright (C) 2019-2021 Jean Pierre Cimalando
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
@@ -384,8 +384,8 @@ void CairoImage::loadFromMemory(const char* const rdata, const Size& s, co
cairo_surface_t* const newsurface = cairo_image_surface_create_for_data(newdata, cairoformat, width, height, stride);
DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,);
- DISTRHO_SAFE_ASSERT_RETURN(s.getWidth() == cairo_image_surface_get_width(newsurface),);
- DISTRHO_SAFE_ASSERT_RETURN(s.getHeight() == cairo_image_surface_get_height(newsurface),);
+ DISTRHO_SAFE_ASSERT_RETURN(static_cast(s.getWidth()) == cairo_image_surface_get_width(newsurface),);
+ DISTRHO_SAFE_ASSERT_RETURN(static_cast(s.getHeight()) == cairo_image_surface_get_height(newsurface),);
cairo_surface_destroy(surface);
diff --git a/dpf/dgl/src/WindowPrivateData.cpp b/dpf/dgl/src/WindowPrivateData.cpp
index 60b8195..6b32e0c 100644
--- a/dpf/dgl/src/WindowPrivateData.cpp
+++ b/dpf/dgl/src/WindowPrivateData.cpp
@@ -31,7 +31,7 @@
START_NAMESPACE_DGL
-#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
+#ifdef DGL_DEBUG_EVENTS
# define DGL_DBG(msg) std::fprintf(stderr, "%s", msg);
# define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__);
# define DGL_DBGF std::fflush(stderr);
@@ -614,7 +614,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh
void Window::PrivateData::onPuglExpose()
{
- DGL_DBG("PUGL: onPuglExpose\n");
+ // DGL_DBG("PUGL: onPuglExpose\n");
puglOnDisplayPrepare(view);
diff --git a/dpf/dgl/src/pugl-upstream/src/wasm.c b/dpf/dgl/src/pugl-upstream/src/wasm.c
index b7d81da..f42aabf 100644
--- a/dpf/dgl/src/pugl-upstream/src/wasm.c
+++ b/dpf/dgl/src/pugl-upstream/src/wasm.c
@@ -23,13 +23,13 @@
PuglWorldInternals*
puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
{
- printf("DONE: %s %d\n", __func__, __LINE__);
-
PuglWorldInternals* impl =
(PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
impl->scaleFactor = emscripten_get_device_pixel_ratio();
+ printf("DONE: %s %d | -> %f\n", __func__, __LINE__, impl->scaleFactor);
+
return impl;
}
@@ -46,6 +46,28 @@ puglInitViewInternals(PuglWorld* const world)
printf("DONE: %s %d\n", __func__, __LINE__);
PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+ impl->buttonPressTimeout = -1;
+ impl->supportsTouch = PUGL_DONT_CARE; // not yet known
+
+#ifdef PUGL_WASM_ASYNC_CLIPBOARD
+ impl->supportsClipboardRead = (PuglViewHintValue)EM_ASM_INT({
+ if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.readText) === 'function' && window.isSecureContext) {
+ return 1; // PUGL_TRUE
+ }
+ return 0; // PUGL_FALSE
+ });
+
+ impl->supportsClipboardWrite = (PuglViewHintValue)EM_ASM_INT({
+ if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
+ return 1; // PUGL_TRUE
+ }
+ if (typeof(document.queryCommandSupported) !== 'undefined' && document.queryCommandSupported("copy")) {
+ return 1; // PUGL_TRUE
+ }
+ return 0; // PUGL_FALSE
+ });
+#endif
+
return impl;
}
@@ -162,7 +184,7 @@ puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEve
PuglEvent event = {{PUGL_NOTHING, 0}};
event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- event.key.time = keyEvent->timestamp / 1000;
+ event.key.time = keyEvent->timestamp / 1e3;
// event.key.x = xevent.xkey.x;
// event.key.y = xevent.xkey.y;
// event.key.xRoot = xevent.xkey.x_root;
@@ -221,7 +243,7 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv
PuglEvent event = {{PUGL_NOTHING, 0}};
- const double time = mouseEvent->timestamp / 1000;
+ const double time = mouseEvent->timestamp / 1e3;
const PuglMods state = translateModifiers(mouseEvent->ctrlKey,
mouseEvent->shiftKey,
mouseEvent->altKey,
@@ -252,30 +274,27 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv
}
break;
case EMSCRIPTEN_EVENT_MOUSEMOVE:
- event.motion.type = PUGL_MOTION;
- event.motion.time = time;
- if (view->impl->lastX == mouseEvent->targetX && view->impl->lastY == mouseEvent->targetY) {
+ event.motion.type = PUGL_MOTION;
+ event.motion.time = time;
+ if (view->impl->pointerLocked) {
// adjust local values for delta
const double movementX = mouseEvent->movementX * scaleFactor;
const double movementY = mouseEvent->movementY * scaleFactor;
- view->impl->lockedX += movementX;
- view->impl->lockedY += movementY;
- view->impl->lockedRootX += movementX;
- view->impl->lockedRootY += movementY;
+ view->impl->lastMotion.x += movementX;
+ view->impl->lastMotion.y += movementY;
+ view->impl->lastMotion.xRoot += movementX;
+ view->impl->lastMotion.yRoot += movementY;
// now set x, y, xRoot and yRoot
- event.motion.x = view->impl->lockedX;
- event.motion.y = view->impl->lockedY;
- event.motion.xRoot = view->impl->lockedRootX;
- event.motion.yRoot = view->impl->lockedRootY;
+ event.motion.x = view->impl->lastMotion.x;
+ event.motion.y = view->impl->lastMotion.y;
+ event.motion.xRoot = view->impl->lastMotion.xRoot;
+ event.motion.yRoot = view->impl->lastMotion.yRoot;
} else {
- // cache unmodified value first, for pointer lock detection
- view->impl->lastX = mouseEvent->targetX;
- view->impl->lastY = mouseEvent->targetY;
- // now set x, y, xRoot and yRoot
- view->impl->lockedX = event.motion.x = mouseEvent->targetX * scaleFactor;
- view->impl->lockedY = event.motion.y = mouseEvent->targetY * scaleFactor;
- view->impl->lockedRootX = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
- view->impl->lockedRootY = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
+ // cache values for possible pointer lock movement later
+ view->impl->lastMotion.x = event.motion.x = mouseEvent->targetX * scaleFactor;
+ view->impl->lastMotion.y = event.motion.y = mouseEvent->targetY * scaleFactor;
+ view->impl->lastMotion.xRoot = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
+ view->impl->lastMotion.yRoot = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
}
event.motion.state = state;
break;
@@ -301,6 +320,119 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv
return EM_FALSE;
}
+static void
+puglTouchStartDelay(void* const userData)
+{
+ PuglView* const view = (PuglView*)userData;
+ PuglInternals* const impl = view->impl;
+
+ impl->buttonPressTimeout = -1;
+ impl->nextButtonEvent.button.time += 2000;
+ puglDispatchEventWithContext(view, &impl->nextButtonEvent);
+}
+
+static EM_BOOL
+puglTouchCallback(const int eventType, const EmscriptenTouchEvent* const touchEvent, void* const userData)
+{
+ if (touchEvent->numTouches <= 0) {
+ return EM_FALSE;
+ }
+
+ PuglView* const view = (PuglView*)userData;
+ PuglInternals* const impl = view->impl;
+
+ if (impl->supportsTouch == PUGL_DONT_CARE) {
+ impl->supportsTouch = PUGL_TRUE;
+
+ // stop using mouse press events which conflict with touch
+ const char* const className = view->world->className;
+ emscripten_set_mousedown_callback(className, view, false, NULL);
+ emscripten_set_mouseup_callback(className, view, false, NULL);
+ }
+
+ if (!view->visible) {
+ return EM_FALSE;
+ }
+
+ PuglEvent event = {{PUGL_NOTHING, 0}};
+
+ const double time = touchEvent->timestamp / 1e3;
+ const PuglMods state = translateModifiers(touchEvent->ctrlKey,
+ touchEvent->shiftKey,
+ touchEvent->altKey,
+ touchEvent->metaKey);
+
+ const double scaleFactor = view->world->impl->scaleFactor;
+
+ d_debug("touch %d|%s %d || %ld",
+ eventType,
+ eventType == EMSCRIPTEN_EVENT_TOUCHSTART ? "start" :
+ eventType == EMSCRIPTEN_EVENT_TOUCHEND ? "end" : "cancel",
+ touchEvent->numTouches,
+ impl->buttonPressTimeout);
+
+ const EmscriptenTouchPoint* point = &touchEvent->touches[0];
+
+ if (impl->buttonPressTimeout != -1 || eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) {
+ // if we received an event while touch is active, trigger initial click now
+ if (impl->buttonPressTimeout != -1) {
+ emscripten_clear_timeout(impl->buttonPressTimeout);
+ impl->buttonPressTimeout = -1;
+ if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) {
+ impl->nextButtonEvent.button.button = 0;
+ }
+ }
+ impl->nextButtonEvent.button.time = time;
+ puglDispatchEventWithContext(view, &impl->nextButtonEvent);
+ }
+
+ switch (eventType) {
+ case EMSCRIPTEN_EVENT_TOUCHEND:
+ case EMSCRIPTEN_EVENT_TOUCHCANCEL:
+ event.button.type = PUGL_BUTTON_RELEASE;
+ event.button.time = time;
+ event.button.button = eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL ? 1 : 0;
+ event.button.x = point->targetX * scaleFactor;
+ event.button.y = point->targetY * scaleFactor;
+ event.button.xRoot = point->screenX * scaleFactor;
+ event.button.yRoot = point->screenY * scaleFactor;
+ event.button.state = state;
+ break;
+
+ case EMSCRIPTEN_EVENT_TOUCHSTART:
+ // this event can be used for a couple of things, store it until we know more
+ event.button.type = PUGL_BUTTON_PRESS;
+ event.button.time = time;
+ event.button.button = 1; // if no other event occurs soon, treat it as right-click
+ event.button.x = point->targetX * scaleFactor;
+ event.button.y = point->targetY * scaleFactor;
+ event.button.xRoot = point->screenX * scaleFactor;
+ event.button.yRoot = point->screenY * scaleFactor;
+ event.button.state = state;
+ memcpy(&impl->nextButtonEvent, &event, sizeof(PuglEvent));
+ impl->buttonPressTimeout = emscripten_set_timeout(puglTouchStartDelay, 2000, view);
+ // fall through, moving "mouse" to touch position
+
+ case EMSCRIPTEN_EVENT_TOUCHMOVE:
+ event.motion.type = PUGL_MOTION;
+ event.motion.time = time;
+ event.motion.x = point->targetX * scaleFactor;
+ event.motion.y = point->targetY * scaleFactor;
+ event.motion.xRoot = point->screenX * scaleFactor;
+ event.motion.yRoot = point->screenY * scaleFactor;
+ event.motion.state = state;
+ break;
+ }
+
+ if (event.type == PUGL_NOTHING)
+ return EM_FALSE;
+
+ puglDispatchEventWithContext(view, &event);
+
+ // FIXME we must always return false??
+ return EM_FALSE;
+}
+
static EM_BOOL
puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData)
{
@@ -324,7 +456,6 @@ puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockCh
{
PuglView* const view = (PuglView*)userData;
- printf("puglPointerLockChangeCallback %d\n", event->isActive);
view->impl->pointerLocked = event->isActive;
return EM_TRUE;
}
@@ -341,7 +472,7 @@ puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEv
const double scaleFactor = view->world->impl->scaleFactor;
PuglEvent event = {{PUGL_SCROLL, 0}};
- event.scroll.time = wheelEvent->mouse.timestamp / 1000;
+ event.scroll.time = wheelEvent->mouse.timestamp / 1e3;
event.scroll.x = wheelEvent->mouse.targetX;
event.scroll.y = wheelEvent->mouse.targetY;
event.scroll.xRoot = wheelEvent->mouse.screenX;
@@ -362,10 +493,19 @@ static EM_BOOL
puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData)
{
PuglView* const view = (PuglView*)userData;
+ const char* const className = view->world->className;
// FIXME
- const int width = EM_ASM_INT({ return canvas.parentElement.clientWidth; });
- const int height = EM_ASM_INT({ return canvas.parentElement.clientHeight; });
+ const int width = EM_ASM_INT({
+ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
+ canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
+ return canvasWrapper.clientWidth;
+ }, className);
+
+ const int height = EM_ASM_INT({
+ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
+ return canvasWrapper.clientHeight;
+ }, className);
if (!width || !height)
return EM_FALSE;
@@ -383,6 +523,17 @@ puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void
return EM_TRUE;
}
+static EM_BOOL
+puglVisibilityChangeCallback(const int eventType, const EmscriptenVisibilityChangeEvent* const visibilityChangeEvent, void* const userData)
+{
+ PuglView* const view = (PuglView*)userData;
+
+ view->visible = visibilityChangeEvent->hidden == EM_FALSE;
+ PuglEvent event = {{ view->visible ? PUGL_MAP : PUGL_UNMAP, 0}};
+ puglDispatchEvent(view, &event);
+ return EM_FALSE;
+}
+
PuglStatus
puglRealize(PuglView* const view)
{
@@ -433,10 +584,19 @@ puglRealize(PuglView* const view)
event.configure.height = view->frame.height;
puglDispatchEvent(view, &event);
+ EM_ASM({
+ var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
+ canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
+ }, className);
+
emscripten_set_canvas_element_size(className, view->frame.width, view->frame.height);
// emscripten_set_keypress_callback(className, view, false, puglKeyCallback);
emscripten_set_keydown_callback(className, view, false, puglKeyCallback);
emscripten_set_keyup_callback(className, view, false, puglKeyCallback);
+ emscripten_set_touchstart_callback(className, view, false, puglTouchCallback);
+ emscripten_set_touchend_callback(className, view, false, puglTouchCallback);
+ emscripten_set_touchmove_callback(className, view, false, puglTouchCallback);
+ emscripten_set_touchcancel_callback(className, view, false, puglTouchCallback);
emscripten_set_mousedown_callback(className, view, false, puglMouseCallback);
emscripten_set_mouseup_callback(className, view, false, puglMouseCallback);
emscripten_set_mousemove_callback(className, view, false, puglMouseCallback);
@@ -444,10 +604,10 @@ puglRealize(PuglView* const view)
emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback);
emscripten_set_focusin_callback(className, view, false, puglFocusCallback);
emscripten_set_focusout_callback(className, view, false, puglFocusCallback);
- emscripten_set_pointerlockchange_callback(className, view, false, puglPointerLockChangeCallback);
emscripten_set_wheel_callback(className, view, false, puglWheelCallback);
+ emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglPointerLockChangeCallback);
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback);
- view->impl->pointerLocked = true;
+ emscripten_set_visibilitychange_callback(view, false, puglVisibilityChangeCallback);
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_SUCCESS;
@@ -456,15 +616,14 @@ puglRealize(PuglView* const view)
PuglStatus
puglShow(PuglView* const view)
{
- printf("TODO: %s %d\n", __func__, __LINE__);
view->visible = true;
+ view->impl->needsRepaint = true;
return puglPostRedisplay(view);
}
PuglStatus
puglHide(PuglView* const view)
{
- printf("TODO: %s %d\n", __func__, __LINE__);
view->visible = false;
return PUGL_FAILURE;
}
@@ -477,6 +636,7 @@ puglFreeViewInternals(PuglView* const view)
if (view->backend) {
view->backend->destroy(view);
}
+ free(view->impl->clipboardData);
free(view->impl->timers);
free(view->impl);
}
@@ -495,36 +655,6 @@ puglGrabFocus(PuglView*)
return PUGL_FAILURE;
}
-PuglStatus
-puglAcceptOffer(PuglView* const view,
- const PuglDataOfferEvent* const offer,
- const uint32_t typeIndex)
-{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_FAILURE;
-}
-
-PuglStatus
-puglPaste(PuglView* const view)
-{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_FAILURE;
-}
-
-uint32_t
-puglGetNumClipboardTypes(const PuglView* const view)
-{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return 0;
-}
-
-const char*
-puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
-{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return NULL;
-}
-
double
puglGetScaleFactor(const PuglView* const view)
{
@@ -535,22 +665,21 @@ puglGetScaleFactor(const PuglView* const view)
double
puglGetTime(const PuglWorld*)
{
-// d_stdout("DONE %s %d", __func__, __LINE__);
- return emscripten_get_now() / 1000;
+ return emscripten_get_now() / 1e3;
}
PuglStatus
puglUpdate(PuglWorld* const world, const double timeout)
{
-// printf("TODO: %s %d\n", __func__, __LINE__);
-
for (size_t i = 0; i < world->numViews; ++i) {
PuglView* const view = world->views[i];
- if (view->visible) {
- puglDispatchSimpleEvent(view, PUGL_UPDATE);
+ if (!view->visible) {
+ continue;
}
+ puglDispatchSimpleEvent(view, PUGL_UPDATE);
+
if (!view->impl->needsRepaint) {
continue;
}
@@ -563,13 +692,6 @@ puglUpdate(PuglWorld* const world, const double timeout)
event.expose.width = view->frame.width;
event.expose.height = view->frame.height;
puglDispatchEvent(view, &event);
-
- static bool p = true;
- if (p) {
- p = false;
- d_stdout("drawing at %d %d %u %u", (int)view->frame.x, (int)view->frame.y,
- (uint)view->frame.width, (uint)view->frame.height);
- }
}
return PUGL_SUCCESS;
@@ -578,7 +700,6 @@ puglUpdate(PuglWorld* const world, const double timeout)
PuglStatus
puglPostRedisplay(PuglView* const view)
{
-// printf("TODO: %s %d\n", __func__, __LINE__);
view->impl->needsRepaint = true;
return PUGL_SUCCESS;
}
@@ -586,7 +707,6 @@ puglPostRedisplay(PuglView* const view)
PuglStatus
puglPostRedisplayRect(PuglView* const view, const PuglRect rect)
{
-// printf("TODO: %s %d\n", __func__, __LINE__);
view->impl->needsRepaint = true;
return PUGL_FAILURE;
}
@@ -594,14 +714,12 @@ puglPostRedisplayRect(PuglView* const view, const PuglRect rect)
PuglNativeView
puglGetNativeView(PuglView* const view)
{
- printf("TODO: %s %d\n", __func__, __LINE__);
return 0;
}
PuglStatus
puglSetWindowTitle(PuglView* const view, const char* const title)
{
- printf("DONE: %s %d\n", __func__, __LINE__);
puglSetString(&view->title, title);
emscripten_set_window_title(title);
return PUGL_SUCCESS;
@@ -613,7 +731,6 @@ puglSetSizeHint(PuglView* const view,
const PuglSpan width,
const PuglSpan height)
{
- printf("DONE: %s %d\n", __func__, __LINE__);
view->sizeHints[hint].width = width;
view->sizeHints[hint].height = height;
return PUGL_SUCCESS;
@@ -659,7 +776,7 @@ puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
timer->view = view;
timer->id = id;
- emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1000, timer);
+ emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1e3, timer);
return PUGL_SUCCESS;
}
@@ -685,13 +802,98 @@ puglStopTimer(PuglView* const view, const uintptr_t id)
return PUGL_FAILURE;
}
+#ifdef PUGL_WASM_ASYNC_CLIPBOARD
+EM_JS(char*, puglGetAsyncClipboardData, (), {
+ var text = Asyncify.handleSleep(function(wakeUp) {
+ navigator.clipboard.readText()
+ .then(function(text) {
+ wakeUp(text);
+ })
+ .catch(function() {
+ wakeUp("");
+ });
+ });
+ if (!text.length) {
+ return null;
+ }
+ var length = lengthBytesUTF8(text) + 1;
+ var str = _malloc(length);
+ stringToUTF8(text, str, length);
+ return str;
+});
+#endif
+
+PuglStatus
+puglPaste(PuglView* const view)
+{
+#ifdef PUGL_WASM_ASYNC_CLIPBOARD
+ // abort early if we already know it is not supported
+ if (view->impl->supportsClipboardRead == PUGL_FALSE) {
+ return PUGL_UNSUPPORTED;
+ }
+
+ free(view->impl->clipboardData);
+ view->impl->clipboardData = puglGetAsyncClipboardData();
+#endif
+
+ if (view->impl->clipboardData == NULL) {
+ return PUGL_FAILURE;
+ }
+
+ const PuglDataOfferEvent offer = {
+ PUGL_DATA_OFFER,
+ 0,
+ emscripten_get_now() / 1e3,
+ };
+
+ PuglEvent offerEvent;
+ offerEvent.offer = offer;
+ puglDispatchEvent(view, &offerEvent);
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglAcceptOffer(PuglView* const view,
+ const PuglDataOfferEvent* const offer,
+ const uint32_t typeIndex)
+{
+ if (typeIndex != 0) {
+ return PUGL_UNSUPPORTED;
+ }
+
+ const PuglDataEvent data = {
+ PUGL_DATA,
+ 0,
+ emscripten_get_now() / 1e3,
+ 0,
+ };
+
+ PuglEvent dataEvent;
+ dataEvent.data = data;
+ puglDispatchEvent(view, &dataEvent);
+ return PUGL_SUCCESS;
+}
+
+uint32_t
+puglGetNumClipboardTypes(const PuglView* const view)
+{
+ return view->impl->clipboardData != NULL ? 1u : 0u;
+}
+
+const char*
+puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
+{
+ return (typeIndex == 0 && view->impl->clipboardData != NULL)
+ ? "text/plain"
+ : NULL;
+}
+
const void*
puglGetClipboard(PuglView* const view,
const uint32_t typeIndex,
size_t* const len)
{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return NULL;
+ return view->impl->clipboardData;
}
PuglStatus
@@ -700,8 +902,48 @@ puglSetClipboard(PuglView* const view,
const void* const data,
const size_t len)
{
- printf("TODO: %s %d\n", __func__, __LINE__);
- return PUGL_FAILURE;
+ // only utf8 text supported for now
+ if (type != NULL && strcmp(type, "text/plain") != 0) {
+ return PUGL_UNSUPPORTED;
+ }
+
+ const char* const className = view->world->className;
+ const char* const text = (const char*)data;
+
+#ifdef PUGL_WASM_ASYNC_CLIPBOARD
+ // abort early if we already know it is not supported
+ if (view->impl->supportsClipboardWrite == PUGL_FALSE) {
+ return PUGL_UNSUPPORTED;
+ }
+#else
+ puglSetString(&view->impl->clipboardData, text);
+#endif
+
+ EM_ASM({
+ if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
+ navigator.clipboard.writeText(UTF8ToString($1));
+ } else {
+ var canvasClipboardObjName = UTF8ToString($0) + "_clipboard";
+ var canvasClipboardElem = document.getElementById(canvasClipboardObjName);
+
+ if (!canvasClipboardElem) {
+ canvasClipboardElem = document.createElement('textarea');
+ canvasClipboardElem.id = canvasClipboardObjName;
+ canvasClipboardElem.style.position = 'fixed';
+ canvasClipboardElem.style.whiteSpace = 'pre';
+ canvasClipboardElem.style.zIndex = '-1';
+ canvasClipboardElem.setAttribute('readonly', true);
+ document.body.appendChild(canvasClipboardElem);
+ }
+
+ canvasClipboardElem.textContent = UTF8ToString($1);
+ canvasClipboardElem.select();
+ document.execCommand("copy");
+ }
+ }, className, text);
+
+ // FIXME proper return status
+ return PUGL_SUCCESS;
}
PuglStatus
diff --git a/dpf/dgl/src/pugl-upstream/src/wasm.h b/dpf/dgl/src/pugl-upstream/src/wasm.h
index 850f62b..ffc0ffb 100644
--- a/dpf/dgl/src/pugl-upstream/src/wasm.h
+++ b/dpf/dgl/src/pugl-upstream/src/wasm.h
@@ -10,6 +10,8 @@
#include "pugl/pugl.h"
+// #define PUGL_WASM_ASYNC_CLIPBOARD
+
struct PuglTimer {
PuglView* view;
uintptr_t id;
@@ -19,14 +21,24 @@ struct PuglWorldInternalsImpl {
double scaleFactor;
};
+struct LastMotionValues {
+ double x, y, xRoot, yRoot;
+};
+
struct PuglInternalsImpl {
PuglSurface* surface;
bool needsRepaint;
bool pointerLocked;
uint32_t numTimers;
- long lastX, lastY;
- double lockedX, lockedY;
- double lockedRootX, lockedRootY;
+ LastMotionValues lastMotion;
+ long buttonPressTimeout;
+ PuglEvent nextButtonEvent;
+#ifdef PUGL_WASM_ASYNC_CLIPBOARD
+ PuglViewHintValue supportsClipboardRead;
+ PuglViewHintValue supportsClipboardWrite;
+#endif
+ PuglViewHintValue supportsTouch;
+ char* clipboardData;
struct PuglTimer* timers;
};
diff --git a/dpf/dgl/src/pugl-upstream/src/wasm_gl.c b/dpf/dgl/src/pugl-upstream/src/wasm_gl.c
index 3b3b9ec..da212bb 100644
--- a/dpf/dgl/src/pugl-upstream/src/wasm_gl.c
+++ b/dpf/dgl/src/pugl-upstream/src/wasm_gl.c
@@ -2,12 +2,9 @@
// Copyright 2021-2022 Filipe Coelho
// SPDX-License-Identifier: ISC
-// #include "attributes.h"
#include "stub.h"
-// #include "types.h"
#include "wasm.h"
-// #include "pugl/gl.h"
#include "pugl/pugl.h"
#include
@@ -15,6 +12,9 @@
#include
+// for performance reasons we can keep a single EGL context always active
+#define PUGL_WASM_SINGLE_EGL_CONTEXT
+
typedef struct {
EGLDisplay display;
EGLConfig config;
@@ -42,21 +42,15 @@ static PuglStatus
puglWasmGlConfigure(PuglView* view)
{
PuglInternals* const impl = view->impl;
-// const int screen = impl->screen;
-// Display* const display = view->world->impl->display;
-
- printf("TODO: %s %d | start\n", __func__, __LINE__);
const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
- printf("eglGetDisplay Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}
int major, minor;
if (eglInitialize(display, &major, &minor) != EGL_TRUE) {
- printf("eglInitialize Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}
@@ -64,33 +58,31 @@ puglWasmGlConfigure(PuglView* view)
int numConfigs;
if (eglGetConfigs(display, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) {
- printf("eglGetConfigs Failed\n");
eglTerminate(display);
return PUGL_CREATE_CONTEXT_FAILED;
}
// clang-format off
const EGLint attrs[] = {
-// GLX_X_RENDERABLE, True,
-// GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
-// GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
-// GLX_RENDER_TYPE, GLX_RGBA_BIT,
-
-// GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]),
-
- EGL_SAMPLES, puglWasmGlHintValue(view->hints[PUGL_SAMPLES]),
- EGL_RED_SIZE, puglWasmGlHintValue(view->hints[PUGL_RED_BITS]),
- EGL_GREEN_SIZE, puglWasmGlHintValue(view->hints[PUGL_GREEN_BITS]),
- EGL_BLUE_SIZE, puglWasmGlHintValue(view->hints[PUGL_BLUE_BITS]),
- EGL_ALPHA_SIZE, puglWasmGlHintValue(view->hints[PUGL_ALPHA_BITS]),
- EGL_DEPTH_SIZE, puglWasmGlHintValue(view->hints[PUGL_DEPTH_BITS]),
- EGL_STENCIL_SIZE, puglWasmGlHintValue(view->hints[PUGL_STENCIL_BITS]),
+ /*
+ GLX_X_RENDERABLE, True,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ EGL_SAMPLE_BUFFERS, view->hints[PUGL_MULTI_SAMPLE] ? 1 : 0,
+ */
+ EGL_SAMPLES, puglWasmGlHintValue(view->hints[PUGL_SAMPLES]),
+ EGL_RED_SIZE, puglWasmGlHintValue(view->hints[PUGL_RED_BITS]),
+ EGL_GREEN_SIZE, puglWasmGlHintValue(view->hints[PUGL_GREEN_BITS]),
+ EGL_BLUE_SIZE, puglWasmGlHintValue(view->hints[PUGL_BLUE_BITS]),
+ EGL_ALPHA_SIZE, puglWasmGlHintValue(view->hints[PUGL_ALPHA_BITS]),
+ EGL_DEPTH_SIZE, puglWasmGlHintValue(view->hints[PUGL_DEPTH_BITS]),
+ EGL_STENCIL_SIZE, puglWasmGlHintValue(view->hints[PUGL_STENCIL_BITS]),
EGL_NONE
};
// clang-format on
if (eglChooseConfig(display, attrs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) {
- printf("eglChooseConfig Failed\n");
eglTerminate(display);
return PUGL_CREATE_CONTEXT_FAILED;
}
@@ -119,11 +111,9 @@ puglWasmGlConfigure(PuglView* view)
view->hints[PUGL_SAMPLES] =
puglWasmGlGetAttrib(display, config, EGL_SAMPLES);
- // always enabled for EGL
+ // double-buffering is always enabled for EGL
view->hints[PUGL_DOUBLE_BUFFER] = 1;
- printf("TODO: %s %d | ok\n", __func__, __LINE__);
-
return PUGL_SUCCESS;
}
@@ -131,39 +121,38 @@ PUGL_WARN_UNUSED_RESULT
static PuglStatus
puglWasmGlEnter(PuglView* view, const PuglExposeEvent* PUGL_UNUSED(expose))
{
-// printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;
if (!surface || !surface->context || !surface->surface) {
return PUGL_FAILURE;
}
- // TESTING: is it faster if we never unset context?
- return PUGL_SUCCESS;
-
+#ifndef PUGL_WASM_SINGLE_EGL_CONTEXT
return eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context) ? PUGL_SUCCESS : PUGL_FAILURE;
+#else
+ return PUGL_SUCCESS;
+#endif
}
PUGL_WARN_UNUSED_RESULT
static PuglStatus
puglWasmGlLeave(PuglView* view, const PuglExposeEvent* expose)
{
-// printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;
if (expose) { // note: swap buffers always enabled for EGL
eglSwapBuffers(surface->display, surface->surface);
}
- // TESTING: is it faster if we never unset context?
- return PUGL_SUCCESS;
-
+#ifndef PUGL_WASM_SINGLE_EGL_CONTEXT
return eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ? PUGL_SUCCESS : PUGL_FAILURE;
+#else
+ return PUGL_SUCCESS;
+#endif
}
static PuglStatus
puglWasmGlCreate(PuglView* view)
{
- printf("TODO: %s %d | start\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;
const EGLDisplay display = surface->display;
const EGLConfig config = surface->config;
@@ -194,31 +183,18 @@ puglWasmGlCreate(PuglView* view)
surface->context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
if (surface->context == EGL_NO_CONTEXT) {
- printf("eglCreateContext Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}
-#if 0
- eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context);
-
- printf("GL_VENDOR=%s\n", glGetString(GL_VENDOR));
- printf("GL_RENDERER=%s\n", glGetString(GL_RENDERER));
- printf("GL_VERSION=%s\n", glGetString(GL_VERSION));
-
- eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-#endif
-
surface->surface = eglCreateWindowSurface(display, config, 0, NULL);
if (surface->surface == EGL_NO_SURFACE) {
- printf("eglCreateWindowSurface Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}
- printf("TODO: %s %d | ok\n", __func__, __LINE__);
-
- // TESTING: is it faster if we never unset context?
+#ifdef PUGL_WASM_SINGLE_EGL_CONTEXT
eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context);
+#endif
return PUGL_SUCCESS;
}
@@ -226,7 +202,6 @@ puglWasmGlCreate(PuglView* view)
static void
puglWasmGlDestroy(PuglView* view)
{
- printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* surface = (PuglWasmGlSurface*)view->impl->surface;
if (surface) {
const EGLDisplay display = surface->display;
@@ -243,13 +218,11 @@ puglWasmGlDestroy(PuglView* view)
const PuglBackend*
puglGlBackend(void)
{
- printf("DONE: %s %d\n", __func__, __LINE__);
static const PuglBackend backend = {puglWasmGlConfigure,
puglWasmGlCreate,
puglWasmGlDestroy,
puglWasmGlEnter,
puglWasmGlLeave,
puglStubGetContext};
-
return &backend;
}
diff --git a/dpf/distrho/DistrhoStandaloneUtils.hpp b/dpf/distrho/DistrhoStandaloneUtils.hpp
index 00d3ada..8fffcda 100644
--- a/dpf/distrho/DistrhoStandaloneUtils.hpp
+++ b/dpf/distrho/DistrhoStandaloneUtils.hpp
@@ -25,11 +25,24 @@ START_NAMESPACE_DISTRHO
* Standalone plugin related utilities */
/**
- @defgroup PluginRelatedUtilities Plugin related utilities
+ @defgroup StandalonePluginRelatedUtilities Plugin related utilities
+
+ When the plugin is running as standalone and JACK is not available, a native audio handling is in place.
+ It is a very simple handling, auto-connecting to the default audio interface for outputs.
+
+ !!EXPERIMENTAL!!
+
+ Still under development and testing.
@{
*/
+/**
+ Check if the current standalone is using native audio methods.
+ If this function returns false, you MUST NOT use any other function from this group.
+*/
+bool isUsingNativeAudio() noexcept;
+
/**
Check if the current standalone supports audio input.
*/
@@ -58,7 +71,7 @@ bool isMIDIEnabled();
/**
Get the current buffer size.
*/
-uint32_t getBufferSize();
+uint getBufferSize();
/**
Request permissions to use audio input.
@@ -69,7 +82,7 @@ bool requestAudioInput();
/**
Request change to a new buffer size.
*/
-bool requestBufferSizeChange(uint32_t newBufferSize);
+bool requestBufferSizeChange(uint newBufferSize);
/**
Request permissions to use MIDI.
diff --git a/dpf/distrho/src/DistrhoPluginJACK.cpp b/dpf/distrho/src/DistrhoPluginJACK.cpp
index 44b932f..3570182 100644
--- a/dpf/distrho/src/DistrhoPluginJACK.cpp
+++ b/dpf/distrho/src/DistrhoPluginJACK.cpp
@@ -31,7 +31,7 @@
# include "../extra/Thread.hpp"
#endif
-#if defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM)
+#if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM)
# define JACKBRIDGE_DIRECT
#endif
diff --git a/dpf/distrho/src/DistrhoPluginVST3.cpp b/dpf/distrho/src/DistrhoPluginVST3.cpp
index a73f860..78c35cd 100644
--- a/dpf/distrho/src/DistrhoPluginVST3.cpp
+++ b/dpf/distrho/src/DistrhoPluginVST3.cpp
@@ -54,7 +54,6 @@
* - bus arrangements
* - optional audio buses
* - routing info, do we care?
- * - set sidechain bus name from port group
* == CV
* - cv scaling to -1/+1
* - test in at least 1 host
@@ -257,14 +256,18 @@ v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instan
* VST3 DSP class.
*
* All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things.
- * The DSP is created during the "initialize" component event, and destroyed during "terminate".
+ * This class is created during the "initialize" component event, and destroyed during "terminate".
*
* The low-level VST3 stuff comes after.
*/
class PluginVst3
{
- /* buses: we provide 1 for the main audio (if there is any) plus 1 for each sidechain or cv port.
- * Main audio comes first, if available.
+ /* Buses: count possible buses we can provide to the host, in case they are not yet defined by the developer.
+ * These values are only used if port groups aren't set.
+ *
+ * When port groups are not in use, we fill in appropriately.
+ * 1 bus is provided for the main audio (if there is any) plus 1 for sidechain and 1 for each cv port.
+ * Main audio is used as first bus, if available.
* Then sidechain, also if available.
* And finally each CV port individually.
*
@@ -273,25 +276,27 @@ class PluginVst3
struct BusInfo {
uint8_t audio; // either 0 or 1
uint8_t sidechain; // either 0 or 1
- uint32_t numMainAudio;
- uint32_t numSidechain;
- uint32_t numCV;
- uint32_t numGroups;
+ uint32_t groups;
+ uint32_t audioPorts;
+ uint32_t sidechainPorts;
+ uint32_t groupPorts;
+ uint32_t cvPorts;
BusInfo()
: audio(0),
sidechain(0),
- numMainAudio(0),
- numSidechain(0),
- numCV(0),
- numGroups(0) {}
+ groups(0),
+ audioPorts(0),
+ sidechainPorts(0),
+ groupPorts(0),
+ cvPorts(0) {}
} inputBuses, outputBuses;
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
- /* handy class for storing and sorting events and MIDI CC parameters
- * only stores events for which a MIDI conversion is possible.
+ /* Handy class for storing and sorting VST3 events and MIDI CC parameters.
+ * It will only store events for which a MIDI conversion is possible.
*/
- struct InputEventsList {
+ struct InputEventList {
enum Type {
NoteOn,
NoteOff,
@@ -313,17 +318,17 @@ class PluginVst3
};
} eventListStorage[kMaxMidiEvents];
- struct InputEventTiming {
+ struct InputEvent {
int32_t sampleOffset;
const InputEventStorage* storage;
- InputEventTiming* next;
+ InputEvent* next;
} eventList[kMaxMidiEvents];
uint16_t numUsed;
int32_t firstSampleOffset;
int32_t lastSampleOffset;
- InputEventTiming* firstEvent;
- InputEventTiming* lastEvent;
+ InputEvent* firstEvent;
+ InputEvent* lastEvent;
void init()
{
@@ -336,7 +341,7 @@ class PluginVst3
{
uint32_t count = 0;
- for (const InputEventTiming* event = firstEvent; event != nullptr; event = event->next)
+ for (const InputEvent* event = firstEvent; event != nullptr; event = event->next)
{
MidiEvent& midiEvent(midiEvents[count++]);
midiEvent.frame = event->sampleOffset;
@@ -491,7 +496,7 @@ class PluginVst3
eventStorage.type = UI_MIDI;
std::memcpy(eventStorage.midi, midiData, sizeof(uint8_t)*3);
- InputEventTiming* const event = &eventList[numUsed];
+ InputEvent* const event = &eventList[numUsed];
event->sampleOffset = 0;
event->storage = &eventStorage;
@@ -514,7 +519,7 @@ class PluginVst3
private:
bool placeSorted(const int32_t sampleOffset) noexcept
{
- InputEventTiming* const event = &eventList[numUsed];
+ InputEvent* const event = &eventList[numUsed];
// initialize
if (numUsed == 0)
@@ -542,7 +547,7 @@ class PluginVst3
else
{
// keep reference out of the loop so we can check validity afterwards
- InputEventTiming* event2 = firstEvent;
+ InputEvent* event2 = firstEvent;
// iterate all events
for (; event2 != nullptr; event2 = event2->next)
@@ -575,139 +580,41 @@ public:
PluginVst3(v3_host_application** const host)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr),
fComponentHandler(nullptr),
-#if DISTRHO_PLUGIN_HAS_UI
-# if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DISTRHO_PLUGIN_HAS_UI
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
fConnectionFromCompToCtrl(nullptr),
-# endif
+ #endif
fConnectionFromCtrlToView(nullptr),
fHostApplication(host),
-#endif
+ #endif
fParameterCount(fPlugin.getParameterCount()),
fVst3ParameterCount(fParameterCount + kVst3InternalParameterCount),
fCachedParameterValues(nullptr),
fDummyAudioBuffer(nullptr),
fParameterValuesChangedDuringProcessing(nullptr)
-#if DISTRHO_PLUGIN_HAS_UI
+ #if DISTRHO_PLUGIN_HAS_UI
, fParameterValueChangesForUI(nullptr)
, fConnectedToUI(false)
-#endif
-#if DISTRHO_PLUGIN_WANT_LATENCY
+ #endif
+ #if DISTRHO_PLUGIN_WANT_LATENCY
, fLastKnownLatency(fPlugin.getLatency())
-#endif
-#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ #endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
, fHostEventOutputHandle(nullptr)
-#endif
-#if DISTRHO_PLUGIN_WANT_PROGRAMS
+ #endif
+ #if DISTRHO_PLUGIN_WANT_PROGRAMS
, fCurrentProgram(0)
, fProgramCountMinusOne(fPlugin.getProgramCount()-1)
-#endif
+ #endif
{
-#if DISTRHO_PLUGIN_NUM_INPUTS > 0
- std::vector visitedInputPortGroups;
- for (uint32_t i=0; i::iterator end = visitedInputPortGroups.end();
- if (std::find(visitedInputPortGroups.begin(), end, port.groupId) == end)
- {
- visitedInputPortGroups.push_back(port.groupId);
- ++inputBuses.numGroups;
- }
- continue;
- }
-
- if (port.hints & kAudioPortIsCV)
- ++inputBuses.numCV;
- else
- ++inputBuses.numMainAudio;
-
- if (port.hints & kAudioPortIsSidechain)
- ++inputBuses.numSidechain;
- }
-
- if (inputBuses.numMainAudio != 0)
- inputBuses.audio = 1;
- if (inputBuses.numSidechain != 0)
- inputBuses.sidechain = 1;
-
- uint32_t cvInputBusId = 0;
- for (uint32_t i=0; i 0
- std::vector visitedOutputPortGroups;
- for (uint32_t i=0; i::iterator end = visitedOutputPortGroups.end();
- if (std::find(visitedOutputPortGroups.begin(), end, port.groupId) == end)
- {
- visitedOutputPortGroups.push_back(port.groupId);
- ++outputBuses.numGroups;
- }
- continue;
- }
-
- if (port.hints & kAudioPortIsCV)
- ++outputBuses.numCV;
- else
- ++outputBuses.numMainAudio;
-
- if (port.hints & kAudioPortIsSidechain)
- ++outputBuses.numSidechain;
- }
-
- if (outputBuses.numMainAudio != 0)
- outputBuses.audio = 1;
- if (outputBuses.numSidechain != 0)
- outputBuses.sidechain = 1;
-
- uint32_t cvOutputBusId = 0;
- for (uint32_t i=0; i 0
+ fillInBusInfoDetails();
+ std::memset(fEnabledInputs, 0, sizeof(fEnabledInputs));
+ #endif
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ fillInBusInfoDetails();
+ std::memset(fEnabledOutputs, 0, sizeof(fEnabledOutputs));
+ #endif
if (const uint32_t extraParameterCount = fParameterCount + kVst3InternalParameterBaseCount)
{
@@ -827,19 +734,19 @@ public:
{
case V3_AUDIO:
if (busDirection == V3_INPUT)
- return inputBuses.audio + inputBuses.sidechain + inputBuses.numCV + inputBuses.numGroups;
+ return inputBuses.audio + inputBuses.sidechain + inputBuses.groups + inputBuses.cvPorts;
if (busDirection == V3_OUTPUT)
- return outputBuses.audio + outputBuses.sidechain + outputBuses.numCV + outputBuses.numGroups;
+ return outputBuses.audio + outputBuses.sidechain + outputBuses.groups + outputBuses.cvPorts;
break;
case V3_EVENT:
-#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
if (busDirection == V3_INPUT)
return 1;
-#endif
-#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ #endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
if (busDirection == V3_OUTPUT)
return 1;
-#endif
+ #endif
break;
}
@@ -855,228 +762,31 @@ public:
DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG);
DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG);
-#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 || DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
- uint32_t busId = static_cast(busIndex);
-#endif
+ #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 || DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ const uint32_t busId = static_cast(busIndex);
+ #endif
if (mediaType == V3_AUDIO)
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
- int32_t numChannels;
- uint32_t flags;
- v3_bus_types busType;
- v3_str_128 busName = {};
-
if (busDirection == V3_INPUT)
{
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
- if (busId < inputBuses.numGroups)
- {
- numChannels = fPlugin.getAudioPortCountWithGroupId(true, busId);
- busType = V3_AUX;
- flags = 0;
-
- for (uint32_t i=0; i(busId, info);
#else
- d_stdout("invalid bus %d", busId);
+ d_stdout("invalid input bus %d", busId);
return V3_INVALID_ARG;
#endif // DISTRHO_PLUGIN_NUM_INPUTS
}
else
{
#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
- if (busId < outputBuses.numGroups)
- {
- numChannels = fPlugin.getAudioPortCountWithGroupId(false, busId);
- busType = V3_AUX;
- flags = 0;
-
- for (uint32_t i=0; i(busId, info);
#else
- d_stdout("invalid bus %d", busId);
+ d_stdout("invalid output bus %d", busId);
return V3_INVALID_ARG;
#endif // DISTRHO_PLUGIN_NUM_OUTPUTS
}
-
- std::memset(info, 0, sizeof(v3_bus_info));
- info->media_type = V3_AUDIO;
- info->direction = busDirection;
- info->channel_count = numChannels;
- std::memcpy(info->bus_name, busName, sizeof(busName));
- info->bus_type = busType;
- info->flags = flags;
- return V3_OK;
#else
d_stdout("invalid bus, line %d", __LINE__);
return V3_INVALID_ARG;
@@ -1459,11 +1169,30 @@ public:
// ----------------------------------------------------------------------------------------------------------------
// v3_audio_processor interface calls
- v3_result setBusArrangements(v3_speaker_arrangement* /*inputs*/, const int32_t /*numInputs*/,
- v3_speaker_arrangement* /*outputs*/, const int32_t /*numOutputs*/)
+ v3_result setBusArrangements(v3_speaker_arrangement* const inputs, const int32_t numInputs,
+ v3_speaker_arrangement* const outputs, const int32_t numOutputs)
{
- // TODO
- return V3_NOT_IMPLEMENTED;
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ DISTRHO_SAFE_ASSERT_RETURN(numInputs >= 0, V3_INVALID_ARG);
+ if (!setAudioBusArrangement(inputs, static_cast(numInputs)))
+ return V3_INTERNAL_ERR;
+ #else
+ DISTRHO_SAFE_ASSERT_RETURN(numInputs == 0, V3_INVALID_ARG);
+ // unused
+ (void)inputs;
+ #endif
+
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ DISTRHO_SAFE_ASSERT_RETURN(numOutputs >= 0, V3_INVALID_ARG);
+ if (!setAudioBusArrangement(outputs, static_cast(numOutputs)))
+ return V3_INTERNAL_ERR;
+ #else
+ DISTRHO_SAFE_ASSERT_RETURN(numOutputs == 0, V3_INVALID_ARG);
+ // unused
+ (void)outputs;
+ #endif
+
+ return V3_OK;
}
v3_result getBusArrangement(const int32_t busDirection, const int32_t busIndex, v3_speaker_arrangement* const speaker) const noexcept
@@ -1473,125 +1202,25 @@ public:
DISTRHO_SAFE_ASSERT_RETURN(speaker != nullptr, V3_INVALID_ARG);
#if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0
- uint32_t busId = static_cast(busIndex);
+ const uint32_t busId = static_cast(busIndex);
#endif
if (busDirection == V3_INPUT)
{
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
- for (uint32_t i=0; i(busId, speaker))
return V3_OK;
- }
- #endif // DISTRHO_PLUGIN_NUM_INPUTS
- d_stdout("invalid bus arrangement %d", busIndex);
+ #endif
+ d_stdout("invalid input bus arrangement %d", busIndex);
return V3_INVALID_ARG;
}
else
{
#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
- for (uint32_t i=0; i(busId, speaker))
return V3_OK;
- }
- #endif // DISTRHO_PLUGIN_NUM_OUTPUTS
- d_stdout("invalid bus arrangement %d", busIndex);
+ #endif
+ d_stdout("invalid output bus arrangement %d", busIndex);
return V3_INVALID_ARG;
}
}
@@ -1661,7 +1290,7 @@ public:
if (! fPlugin.isActive())
fPlugin.activate();
-#if DISTRHO_PLUGIN_WANT_TIMEPOS
+ #if DISTRHO_PLUGIN_WANT_TIMEPOS
if (v3_process_context* const ctx = data->ctx)
{
fTimePosition.playing = ctx->state & V3_PROCESS_CTX_PLAYING;
@@ -1716,7 +1345,7 @@ public:
fPlugin.setTimePosition(fTimePosition);
}
-#endif
+ #endif
if (data->nframes <= 0)
{
@@ -2648,81 +2277,448 @@ public:
}
}
- d_stderr("Failed to find plugin state with key \"%s\"", key);
+ d_stderr("Failed to find plugin state with key \"%s\"", key);
+ }
+
+ std::free(key16);
+ std::free(value16);
+ return V3_OK;
+ }
+ #endif // DISTRHO_PLUGIN_WANT_STATE
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ v3_result notify_midi(v3_attribute_list** const attrs)
+ {
+ uint8_t* data;
+ uint32_t size;
+ v3_result res;
+
+ res = v3_cpp_obj(attrs)->get_binary(attrs, "data", (const void**)&data, &size);
+ DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+
+ // known maximum size
+ DISTRHO_SAFE_ASSERT_UINT_RETURN(size == 3, size, V3_INTERNAL_ERR);
+
+ return fNotesRingBuffer.writeCustomData(data, size) && fNotesRingBuffer.commitWrite() ? V3_OK : V3_NOMEM;
+ }
+ #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT
+#endif
+
+ // ----------------------------------------------------------------------------------------------------------------
+
+private:
+ // Plugin
+ PluginExporter fPlugin;
+
+ // VST3 stuff
+ v3_component_handler** fComponentHandler;
+ #if DISTRHO_PLUGIN_HAS_UI
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
+ v3_connection_point** fConnectionFromCompToCtrl;
+ #endif
+ v3_connection_point** fConnectionFromCtrlToView;
+ v3_host_application** const fHostApplication;
+ #endif
+
+ // Temporary data
+ const uint32_t fParameterCount;
+ const uint32_t fVst3ParameterCount; // full offset + real
+ float* fCachedParameterValues; // basic offset + real
+ float* fDummyAudioBuffer;
+ bool* fParameterValuesChangedDuringProcessing; // basic offset + real
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ bool fEnabledInputs[DISTRHO_PLUGIN_NUM_INPUTS];
+ #endif
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ bool fEnabledOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS];
+ #endif
+ #if DISTRHO_PLUGIN_HAS_UI
+ bool* fParameterValueChangesForUI; // basic offset + real
+ bool fConnectedToUI;
+ #endif
+ #if DISTRHO_PLUGIN_WANT_LATENCY
+ uint32_t fLastKnownLatency;
+ #endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ MidiEvent fMidiEvents[kMaxMidiEvents];
+ #if DISTRHO_PLUGIN_HAS_UI
+ SmallStackRingBuffer fNotesRingBuffer;
+ #endif
+ #endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ v3_event_list** fHostEventOutputHandle;
+ #endif
+ #if DISTRHO_PLUGIN_WANT_PROGRAMS
+ uint32_t fCurrentProgram;
+ const uint32_t fProgramCountMinusOne;
+ #endif
+ #if DISTRHO_PLUGIN_WANT_STATE
+ StringMap fStateMap;
+ #endif
+ #if DISTRHO_PLUGIN_WANT_TIMEPOS
+ TimePosition fTimePosition;
+ #endif
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // helper functions for dealing with buses
+
+ #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ template
+ void fillInBusInfoDetails()
+ {
+ constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
+ BusInfo& busInfo(isInput ? inputBuses : outputBuses);
+ bool* const enabledPorts = isInput
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ ? fEnabledInputs
+ #else
+ ? nullptr
+ #endif
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ : fEnabledOutputs;
+ #else
+ : nullptr;
+ #endif
+
+ std::vector visitedPortGroups;
+ for (uint32_t i=0; i::iterator end = visitedPortGroups.end();
+ if (std::find(visitedPortGroups.begin(), end, port.groupId) == end)
+ {
+ visitedPortGroups.push_back(port.groupId);
+ ++busInfo.groups;
+ }
+ ++busInfo.groupPorts;
+ continue;
+ }
+
+ if (port.hints & kAudioPortIsCV)
+ ++busInfo.cvPorts;
+ else if (port.hints & kAudioPortIsSidechain)
+ ++busInfo.sidechainPorts;
+ else
+ ++busInfo.audioPorts;
+ }
+
+ if (busInfo.audioPorts != 0)
+ busInfo.audio = 1;
+ if (busInfo.sidechainPorts != 0)
+ busInfo.sidechain = 1;
+
+ uint32_t busIdFromGroup = 0;
+ uint32_t busIdForCV = 0;
+ for (uint32_t i=0; i
+ v3_result getAudioBusInfo(uint32_t busId, v3_bus_info* const info) const
+ {
+ constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
+ const BusInfo& busInfo(isInput ? inputBuses : outputBuses);
+
+ int32_t numChannels;
+ uint32_t flags;
+ v3_bus_types busType;
+ v3_str_128 busName = {};
+
+ if (busId < busInfo.groups)
+ {
+ numChannels = fPlugin.getAudioPortCountWithGroupId(isInput, busId);
+ busType = V3_AUX;
+ flags = 0;
+
+ for (uint32_t i=0; imedia_type = V3_AUDIO;
+ info->direction = isInput ? V3_INPUT : V3_OUTPUT;
+ info->channel_count = numChannels;
+ std::memcpy(info->bus_name, busName, sizeof(busName));
+ info->bus_type = busType;
+ info->flags = flags;
+ return V3_OK;
+ }
+
+ template
+ bool getAudioBusArrangement(uint32_t busId, v3_speaker_arrangement* const speaker) const
+ {
+ constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
+ const BusInfo& busInfo(isInput ? inputBuses : outputBuses);
+ const bool* const enabledPorts = isInput
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ ? fEnabledInputs
+ #else
+ ? nullptr
+ #endif
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ : fEnabledOutputs;
+ #else
+ : nullptr;
+ #endif
+
+ for (uint32_t i=0; i
+ bool setAudioBusArrangement(v3_speaker_arrangement* const speakers, const uint32_t numBuses)
{
- uint8_t* data;
- uint32_t size;
- v3_result res;
+ constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
+ BusInfo& busInfo(isInput ? inputBuses : outputBuses);
+ bool* const enabledPorts = isInput
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ ? fEnabledInputs
+ #else
+ ? nullptr
+ #endif
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ : fEnabledOutputs;
+ #else
+ : nullptr;
+ #endif
+
+ for (uint32_t busId=0; busIdget_binary(attrs, "data", (const void**)&data, &size);
- DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+ // d_stdout("setAudioBusArrangement %d %d | %d %lx", (int)isInput, numBuses, busId, arr);
- // known maximum size
- DISTRHO_SAFE_ASSERT_UINT_RETURN(size == 3, size, V3_INTERNAL_ERR);
+ for (uint32_t i=0; irequestParameterValueChange(index, value);
}
-#endif
+ #endif
-#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
bool writeMidi(const MidiEvent& midiEvent)
{
DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fHostEventOutputHandle != nullptr, false);
@@ -2976,8 +2972,7 @@ private:
{
return ((PluginVst3*)ptr)->writeMidi(midiEvent);
}
-#endif
-
+ #endif
};
// --------------------------------------------------------------------------------------------------------------------
@@ -3283,41 +3278,41 @@ struct dpf_midi_mapping : v3_midi_mapping_cpp {
struct dpf_edit_controller : v3_edit_controller_cpp {
std::atomic_int refcounter;
-#if DISTRHO_PLUGIN_HAS_UI
+ #if DISTRHO_PLUGIN_HAS_UI
ScopedPointer connectionCtrl2View;
-#endif
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #endif
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
ScopedPointer connectionComp2Ctrl;
ScopedPointer vst3;
-#else
+ #else
ScopedPointer& vst3;
bool initialized;
-#endif
+ #endif
// cached values
v3_component_handler** handler;
v3_host_application** const hostApplicationFromFactory;
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
v3_host_application** const hostApplicationFromComponent;
v3_host_application** hostApplicationFromComponentInitialize;
-#endif
+ #endif
v3_host_application** hostApplicationFromInitialize;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
dpf_edit_controller(v3_host_application** const hostApp)
: refcounter(1),
vst3(nullptr),
-#else
+ #else
dpf_edit_controller(ScopedPointer& v, v3_host_application** const hostApp, v3_host_application** const hostComp)
: refcounter(1),
vst3(v),
initialized(false),
-#endif
+ #endif
handler(nullptr),
hostApplicationFromFactory(hostApp),
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
hostApplicationFromComponent(hostComp),
hostApplicationFromComponentInitialize(nullptr),
-#endif
+ #endif
hostApplicationFromInitialize(nullptr)
{
d_stdout("dpf_edit_controller() with hostApplication %p", hostApplicationFromFactory);
@@ -3325,10 +3320,10 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
// make sure host application is valid through out this controller lifetime
if (hostApplicationFromFactory != nullptr)
v3_cpp_obj_ref(hostApplicationFromFactory);
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
if (hostApplicationFromComponent != nullptr)
v3_cpp_obj_ref(hostApplicationFromComponent);
-#endif
+ #endif
// v3_funknown, everything custom
query_interface = query_interface_edit_controller;
@@ -3358,18 +3353,18 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
~dpf_edit_controller()
{
d_stdout("~dpf_edit_controller()");
-#if DISTRHO_PLUGIN_HAS_UI
+ #if DISTRHO_PLUGIN_HAS_UI
connectionCtrl2View = nullptr;
-#endif
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #endif
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
connectionComp2Ctrl = nullptr;
vst3 = nullptr;
-#endif
+ #endif
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
if (hostApplicationFromComponent != nullptr)
v3_cpp_obj_unref(hostApplicationFromComponent);
-#endif
+ #endif
if (hostApplicationFromFactory != nullptr)
v3_cpp_obj_unref(hostApplicationFromFactory);
}
@@ -3393,22 +3388,22 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
if (v3_tuid_match(iid, v3_midi_mapping_iid))
{
-#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
d_stdout("query_interface_edit_controller => %p %s %p | OK convert static", self, tuid2str(iid), iface);
static dpf_midi_mapping midi_mapping;
static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping;
*iface = &midi_mapping_ptr;
return V3_OK;
-#else
+ #else
d_stdout("query_interface_edit_controller => %p %s %p | reject unused", self, tuid2str(iid), iface);
*iface = nullptr;
return V3_NO_INTERFACE;
-#endif
+ #endif
}
if (v3_tuid_match(iid, v3_connection_point_iid))
{
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
d_stdout("query_interface_edit_controller => %p %s %p | OK convert %p",
self, tuid2str(iid), iface, controller->connectionComp2Ctrl.get());
@@ -3418,11 +3413,11 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
++controller->connectionComp2Ctrl->refcounter;
*iface = &controller->connectionComp2Ctrl;
return V3_OK;
-#else
+ #else
d_stdout("query_interface_edit_controller => %p %s %p | reject unwanted", self, tuid2str(iid), iface);
*iface = nullptr;
return V3_NO_INTERFACE;
-#endif
+ #endif
}
d_stdout("query_interface_edit_controller => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
@@ -3449,7 +3444,7 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
return refcount;
}
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
/**
* Some hosts will have unclean instances of a few of the controller child classes at this point.
* We check for those here, going through the whole possible chain to see if it is safe to delete.
@@ -3474,9 +3469,9 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
delete controller;
delete controllerptr;
-#else
+ #else
d_stdout("dpf_edit_controller::unref => %p | refcount is zero, deletion will be done by component later", self);
-#endif
+ #endif
return 0;
}
@@ -3488,11 +3483,11 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
dpf_edit_controller* const controller = *static_cast(self);
// check if already initialized
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 == nullptr, V3_INVALID_ARG);
-#else
+ #else
DISTRHO_SAFE_ASSERT_RETURN(! controller->initialized, V3_INVALID_ARG);
-#endif
+ #endif
// query for host application
v3_host_application** hostApplication = nullptr;
@@ -3504,7 +3499,7 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
// save it for later so we can unref it
controller->hostApplicationFromInitialize = hostApplication;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
// provide the factory application to the plugin if this new one is missing
if (hostApplication == nullptr)
hostApplication = controller->hostApplicationFromFactory;
@@ -3526,10 +3521,10 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
if (point->other != nullptr)
controller->vst3->comp2ctrl_connect(point->other);
}
-#else
+ #else
// mark as initialized
controller->initialized = true;
-#endif
+ #endif
return V3_OK;
}
@@ -3539,19 +3534,19 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
d_stdout("dpf_edit_controller::terminate => %p", self);
dpf_edit_controller* const controller = *static_cast(self);
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
// check if already terminated
DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_INVALID_ARG);
// delete actual plugin
controller->vst3 = nullptr;
-#else
+ #else
// check if already terminated
DISTRHO_SAFE_ASSERT_RETURN(controller->initialized, V3_INVALID_ARG);
// mark as uninitialzed
controller->initialized = false;
-#endif
+ #endif
// unref host application received during initialize
if (controller->hostApplicationFromInitialize != nullptr)
@@ -3570,30 +3565,30 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
{
d_stdout("dpf_edit_controller::set_component_state => %p %p", self, stream);
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
dpf_edit_controller* const controller = *static_cast(self);
PluginVst3* const vst3 = controller->vst3;
DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED);
return vst3->setState(stream);
-#else
+ #else
return V3_OK;
// unused
(void)self;
(void)stream;
-#endif
+ #endif
}
static v3_result V3_API set_state(void* const self, v3_bstream** const stream)
{
d_stdout("dpf_edit_controller::set_state => %p %p", self, stream);
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
dpf_edit_controller* const controller = *static_cast(self);
DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED);
-#endif
+ #endif
return V3_NOT_IMPLEMENTED;
@@ -3606,10 +3601,10 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
{
d_stdout("dpf_edit_controller::get_state => %p %p", self, stream);
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
dpf_edit_controller* const controller = *static_cast(self);
DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED);
-#endif
+ #endif
return V3_NOT_IMPLEMENTED;
@@ -3727,7 +3722,7 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
d_stdout("create_view has contexts %p %p",
controller->hostApplicationFromFactory, controller->hostApplicationFromInitialize);
-#if DISTRHO_PLUGIN_HAS_UI
+ #if DISTRHO_PLUGIN_HAS_UI
// plugin must be initialized
PluginVst3* const vst3 = controller->vst3;
DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, nullptr);
@@ -3740,12 +3735,12 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
// we require a host application for message creation
v3_host_application** const host = controller->hostApplicationFromInitialize != nullptr
? controller->hostApplicationFromInitialize
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
: controller->hostApplicationFromComponent != nullptr
? controller->hostApplicationFromComponent
: controller->hostApplicationFromComponentInitialize != nullptr
? controller->hostApplicationFromComponentInitialize
-#endif
+ #endif
: controller->hostApplicationFromFactory;
DISTRHO_SAFE_ASSERT_RETURN(host != nullptr, nullptr);
@@ -3771,9 +3766,9 @@ struct dpf_edit_controller : v3_edit_controller_cpp {
}
return view;
-#else
+ #else
return nullptr;
-#endif
+ #endif
}
};
@@ -3816,16 +3811,16 @@ struct dpf_process_context_requirements : v3_process_context_requirements_cpp {
static uint32_t V3_API get_process_context_requirements(void*)
{
-#if DISTRHO_PLUGIN_WANT_TIMEPOS
+ #if DISTRHO_PLUGIN_WANT_TIMEPOS
return 0x0
| V3_PROCESS_CTX_NEED_CONTINUOUS_TIME // V3_PROCESS_CTX_CONT_TIME_VALID
| V3_PROCESS_CTX_NEED_PROJECT_TIME // V3_PROCESS_CTX_PROJECT_TIME_VALID
| V3_PROCESS_CTX_NEED_TEMPO // V3_PROCESS_CTX_TEMPO_VALID
| V3_PROCESS_CTX_NEED_TIME_SIG // V3_PROCESS_CTX_TIME_SIG_VALID
| V3_PROCESS_CTX_NEED_TRANSPORT_STATE; // V3_PROCESS_CTX_PLAYING
-#else
+ #else
return 0x0;
-#endif
+ #endif
}
DISTRHO_PREVENT_HEAP_ALLOCATION
@@ -3896,9 +3891,9 @@ struct dpf_audio_processor : v3_audio_processor_cpp {
v3_speaker_arrangement* const inputs, const int32_t num_inputs,
v3_speaker_arrangement* const outputs, const int32_t num_outputs)
{
- // NOTE this is called a bunch of times
- // d_stdout("dpf_audio_processor::set_bus_arrangements => %p %p %i %p %i",
- // self, inputs, num_inputs, outputs, num_outputs);
+ // NOTE this is called a bunch of times in JUCE hosts
+ d_stdout("dpf_audio_processor::set_bus_arrangements => %p %p %i %p %i",
+ self, inputs, num_inputs, outputs, num_outputs);
dpf_audio_processor* const processor = *static_cast(self);
PluginVst3* const vst3 = processor->vst3;
@@ -3994,11 +3989,11 @@ struct dpf_audio_processor : v3_audio_processor_cpp {
struct dpf_component : v3_component_cpp {
std::atomic_int refcounter;
ScopedPointer processor;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
ScopedPointer connectionComp2Ctrl;
-#else
+ #else
ScopedPointer controller;
-#endif
+ #endif
ScopedPointer vst3;
v3_host_application** const hostApplicationFromFactory;
v3_host_application** hostApplicationFromInitialize;
@@ -4039,11 +4034,11 @@ struct dpf_component : v3_component_cpp {
{
d_stdout("~dpf_component()");
processor = nullptr;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
connectionComp2Ctrl = nullptr;
-#else
+ #else
controller = nullptr;
-#endif
+ #endif
vst3 = nullptr;
if (hostApplicationFromFactory != nullptr)
@@ -4069,17 +4064,17 @@ struct dpf_component : v3_component_cpp {
if (v3_tuid_match(iid, v3_midi_mapping_iid))
{
-#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
d_stdout("query_interface_component => %p %s %p | OK convert static", self, tuid2str(iid), iface);
static dpf_midi_mapping midi_mapping;
static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping;
*iface = &midi_mapping_ptr;
return V3_OK;
-#else
+ #else
d_stdout("query_interface_component => %p %s %p | reject unused", self, tuid2str(iid), iface);
*iface = nullptr;
return V3_NO_INTERFACE;
-#endif
+ #endif
}
if (v3_tuid_match(iid, v3_audio_processor_iid))
@@ -4097,7 +4092,7 @@ struct dpf_component : v3_component_cpp {
if (v3_tuid_match(iid, v3_connection_point_iid))
{
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
d_stdout("query_interface_component => %p %s %p | OK convert %p",
self, tuid2str(iid), iface, component->connectionComp2Ctrl.get());
@@ -4107,16 +4102,16 @@ struct dpf_component : v3_component_cpp {
++component->connectionComp2Ctrl->refcounter;
*iface = &component->connectionComp2Ctrl;
return V3_OK;
-#else
+ #else
d_stdout("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface);
*iface = nullptr;
return V3_NO_INTERFACE;
-#endif
+ #endif
}
if (v3_tuid_match(iid, v3_edit_controller_iid))
{
-#if !DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if !DPF_VST3_USES_SEPARATE_CONTROLLER
d_stdout("query_interface_component => %p %s %p | OK convert %p",
self, tuid2str(iid), iface, component->controller.get());
@@ -4128,11 +4123,11 @@ struct dpf_component : v3_component_cpp {
++component->controller->refcounter;
*iface = &component->controller;
return V3_OK;
-#else
+ #else
d_stdout("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface);
*iface = nullptr;
return V3_NO_INTERFACE;
-#endif
+ #endif
}
d_stdout("query_interface_component => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
@@ -4176,7 +4171,7 @@ struct dpf_component : v3_component_cpp {
}
}
-#if DPF_VST3_USES_SEPARATE_CONTROLLER
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
if (dpf_comp2ctrl_connection_point* const point = component->connectionComp2Ctrl)
{
if (const int refcount = point->refcounter)
@@ -4185,7 +4180,7 @@ struct dpf_component : v3_component_cpp {
d_stderr("DPF warning: asked to delete component while connection point still active (refcount %d)", refcount);
}
}
-#else
+ #else
if (dpf_edit_controller* const controller = component->controller)
{
if (const int refcount = controller->refcounter)
@@ -4194,7 +4189,7 @@ struct dpf_component : v3_component_cpp {
d_stderr("DPF warning: asked to delete component while edit controller still active (refcount %d)", refcount);
}
}
-#endif
+ #endif
if (unclean)
return handleUncleanComponent(componentptr);
@@ -4609,19 +4604,33 @@ struct dpf_factory : v3_plugin_factory_cpp {
static int32_t V3_API num_classes(void*)
{
d_stdout("dpf_factory::num_classes");
- return 1;
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER
+ return 2; // factory can create component and edit-controller
+ #else
+ return 1; // factory can only create component, edit-controller must be casted
+ #endif
}
static v3_result V3_API get_class_info(void*, const int32_t idx, v3_class_info* const info)
{
d_stdout("dpf_factory::get_class_info => %i %p", idx, info);
std::memset(info, 0, sizeof(*info));
- DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG);
+ DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG);
info->cardinality = 0x7FFFFFFF;
- std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
- DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name));
+
+ if (idx == 0)
+ {
+ std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
+ }
+ else
+ {
+ std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category));
+ }
+
return V3_OK;
}
@@ -4671,19 +4680,29 @@ struct dpf_factory : v3_plugin_factory_cpp {
{
d_stdout("dpf_factory::get_class_info_2 => %i %p", idx, info);
std::memset(info, 0, sizeof(*info));
- DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG);
+ DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG);
info->cardinality = 0x7FFFFFFF;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI
info->class_flags = V3_DISTRIBUTABLE;
-#endif
- std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
- DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
+ #endif
DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories));
DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name));
DISTRHO_NAMESPACE::strncpy(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor));
DISTRHO_NAMESPACE::strncpy(info->version, getPluginVersion(), ARRAY_SIZE(info->version));
DISTRHO_NAMESPACE::strncpy(info->sdk_version, "Travesty 3.7.4", ARRAY_SIZE(info->sdk_version));
+
+ if (idx == 0)
+ {
+ std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
+ }
+ else
+ {
+ std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category));
+ }
+
return V3_OK;
}
@@ -4694,19 +4713,29 @@ struct dpf_factory : v3_plugin_factory_cpp {
{
d_stdout("dpf_factory::get_class_info_utf16 => %i %p", idx, info);
std::memset(info, 0, sizeof(*info));
- DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG);
+ DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG);
info->cardinality = 0x7FFFFFFF;
-#if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI
+ #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI
info->class_flags = V3_DISTRIBUTABLE;
-#endif
- std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
- DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
+ #endif
DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories));
DISTRHO_NAMESPACE::strncpy_utf16(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name));
DISTRHO_NAMESPACE::strncpy_utf16(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor));
DISTRHO_NAMESPACE::strncpy_utf16(info->version, getPluginVersion(), ARRAY_SIZE(info->version));
DISTRHO_NAMESPACE::strncpy_utf16(info->sdk_version, "Travesty 3.7.4", ARRAY_SIZE(info->sdk_version));
+
+ if (idx == 0)
+ {
+ std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category));
+ }
+ else
+ {
+ std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid));
+ DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category));
+ }
+
return V3_OK;
}
@@ -4777,11 +4806,17 @@ bool ENTRYFNNAME(ENTRYFNNAMEARGS)
String tmpPath(getBinaryFilename());
tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
- DISTRHO_SAFE_ASSERT_RETURN(tmpPath.endsWith(DISTRHO_OS_SEP_STR "Contents"), true);
- tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
- bundlePath = tmpPath;
- d_nextBundlePath = bundlePath.buffer();
+ if (tmpPath.endsWith(DISTRHO_OS_SEP_STR "Contents"))
+ {
+ tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
+ bundlePath = tmpPath;
+ d_nextBundlePath = bundlePath.buffer();
+ }
+ else
+ {
+ bundlePath = "error";
+ }
}
// init dummy plugin and set uniqueId
diff --git a/dpf/distrho/src/DistrhoUtils.cpp b/dpf/distrho/src/DistrhoUtils.cpp
index fc0ff25..aeb7b2b 100644
--- a/dpf/distrho/src/DistrhoUtils.cpp
+++ b/dpf/distrho/src/DistrhoUtils.cpp
@@ -19,6 +19,7 @@
#endif
#include "../extra/String.hpp"
+#include "../DistrhoStandaloneUtils.hpp"
#ifdef DISTRHO_OS_WINDOWS
# include
@@ -141,6 +142,20 @@ const char* getResourcePath(const char* const bundlePath) noexcept
return nullptr;
}
+#ifndef DISTRHO_PLUGIN_TARGET_JACK
+// all these are null for non-standalone targets
+bool isUsingNativeAudio() noexcept { return false; }
+bool supportsAudioInput() { return false; }
+bool supportsBufferSizeChanges() { return false; }
+bool supportsMIDI() { return false; }
+bool isAudioInputEnabled() { return false; }
+bool isMIDIEnabled() { return false; }
+uint getBufferSize() { return 0; }
+bool requestAudioInput() { return false; }
+bool requestBufferSizeChange(uint) { return false; }
+bool requestMIDI() { return false; }
+#endif
+
// -----------------------------------------------------------------------
END_NAMESPACE_DISTRHO
diff --git a/dpf/distrho/src/jackbridge/JackBridge.cpp b/dpf/distrho/src/jackbridge/JackBridge.cpp
index 02c3049..1538f67 100644
--- a/dpf/distrho/src/jackbridge/JackBridge.cpp
+++ b/dpf/distrho/src/jackbridge/JackBridge.cpp
@@ -34,7 +34,12 @@
#endif
#include
-#include "../../extra/LibraryUtils.hpp"
+
+#ifdef HAVE_JACK
+# include "../../extra/LibraryUtils.hpp"
+#else
+typedef void* lib_t;
+#endif
// in case JACK fails, we fallback to native bridges simulating JACK API
#include "NativeBridge.hpp"
@@ -56,10 +61,18 @@
#endif
#if defined(HAVE_RTAUDIO) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+// fix conflict between DGL and macOS names
+# define Point CorePoint
+# define Size CoreSize
# include "RtAudioBridge.hpp"
# ifdef RTAUDIO_API_TYPE
# include "rtaudio/RtAudio.cpp"
# endif
+# ifdef RTMIDI_API_TYPE
+# include "rtmidi/RtMidi.cpp"
+# endif
+# undef Point
+# undef Size
#endif
#if defined(HAVE_SDL2) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
@@ -331,9 +344,9 @@ struct JackBridge {
jacksym_remove_all_properties remove_all_properties_ptr;
jacksym_set_property_change_callback set_property_change_callback_ptr;
-#ifdef __WINE__
+ #ifdef __WINE__
jacksym_set_thread_creator set_thread_creator_ptr;
-#endif
+ #endif
JackBridge()
: lib(nullptr),
@@ -429,15 +442,11 @@ struct JackBridge {
remove_properties_ptr(nullptr),
remove_all_properties_ptr(nullptr),
set_property_change_callback_ptr(nullptr)
-#ifdef __WINE__
+ #ifdef __WINE__
, set_thread_creator_ptr(nullptr)
-#endif
+ #endif
{
- #ifdef DISTRHO_OS_WASM
- // never use jack in wasm
- return;
- #endif
-
+ #ifdef HAVE_JACK
#if defined(DISTRHO_OS_MAC)
const char* const filename = "libjack.dylib";
#elif defined(DISTRHO_OS_WINDOWS) && defined(_WIN64)
@@ -578,14 +587,16 @@ struct JackBridge {
LIB_SYMBOL(remove_all_properties)
LIB_SYMBOL(set_property_change_callback)
-#ifdef __WINE__
+ #ifdef __WINE__
LIB_SYMBOL(set_thread_creator)
-#endif
+ #endif
+ #endif
#undef JOIN
#undef LIB_SYMBOL
}
+ #ifdef HAVE_JACK
~JackBridge() noexcept
{
USE_NAMESPACE_DISTRHO
@@ -596,6 +607,7 @@ struct JackBridge {
lib = nullptr;
}
}
+ #endif
DISTRHO_DECLARE_NON_COPYABLE(JackBridge);
};
@@ -2265,15 +2277,22 @@ bool jackbridge_set_property_change_callback(jack_client_t* client, JackProperty
START_NAMESPACE_DISTRHO
-bool supportsAudioInput()
+bool isUsingNativeAudio() noexcept
{
-#if defined(JACKBRIDGE_DUMMY)
+#if defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)
return false;
-#elif !defined(JACKBRIDGE_DIRECT)
+#else
+ return usingNativeBridge;
+#endif
+}
+
+bool supportsAudioInput()
+{
+#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
return nativeBridge->supportsAudioInput();
#endif
- return true;
+ return false;
}
bool supportsBufferSizeChanges()
@@ -2287,38 +2306,32 @@ bool supportsBufferSizeChanges()
bool supportsMIDI()
{
-#if defined(JACKBRIDGE_DUMMY)
- return false;
-#elif !defined(JACKBRIDGE_DIRECT)
+#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
return nativeBridge->supportsMIDI();
#endif
- return true;
+ return false;
}
bool isAudioInputEnabled()
{
-#if defined(JACKBRIDGE_DUMMY)
- return false;
-#elif !defined(JACKBRIDGE_DIRECT)
+#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
return nativeBridge->isAudioInputEnabled();
#endif
- return true;
+ return false;
}
bool isMIDIEnabled()
{
-#if defined(JACKBRIDGE_DUMMY)
- return false;
-#elif !defined(JACKBRIDGE_DIRECT)
+#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
return nativeBridge->isMIDIEnabled();
#endif
- return true;
+ return false;
}
-uint32_t getBufferSize()
+uint getBufferSize()
{
#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
@@ -2336,7 +2349,7 @@ bool requestAudioInput()
return false;
}
-bool requestBufferSizeChange(const uint32_t newBufferSize)
+bool requestBufferSizeChange(const uint newBufferSize)
{
#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT))
if (usingNativeBridge)
diff --git a/dpf/distrho/src/jackbridge/NativeBridge.hpp b/dpf/distrho/src/jackbridge/NativeBridge.hpp
index 30e26db..b30e8bf 100644
--- a/dpf/distrho/src/jackbridge/NativeBridge.hpp
+++ b/dpf/distrho/src/jackbridge/NativeBridge.hpp
@@ -205,10 +205,12 @@ struct NativeBridge {
(void)time;
}
- void allocBuffers()
+ void allocBuffers(const bool audio, const bool midi)
{
DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,);
+ if (audio)
+ {
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)];
@@ -219,7 +221,10 @@ struct NativeBridge {
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS);
#endif
+ }
+ if (midi)
+ {
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512);
midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512);
@@ -227,6 +232,7 @@ struct NativeBridge {
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
midiOutBuffer.createBuffer(2048);
#endif
+ }
}
void freeBuffers()
diff --git a/dpf/distrho/src/jackbridge/RtAudioBridge.hpp b/dpf/distrho/src/jackbridge/RtAudioBridge.hpp
index 146ed14..2d37133 100644
--- a/dpf/distrho/src/jackbridge/RtAudioBridge.hpp
+++ b/dpf/distrho/src/jackbridge/RtAudioBridge.hpp
@@ -26,28 +26,54 @@
#if defined(DISTRHO_OS_MAC)
# define __MACOSX_CORE__
# define RTAUDIO_API_TYPE MACOSX_CORE
+# define RTMIDI_API_TYPE MACOSX_CORE
#elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER)
# define __WINDOWS_DS__
# define RTAUDIO_API_TYPE WINDOWS_DS
-#elif defined(HAVE_PULSEAUDIO)
-# define __LINUX_PULSE__
-# define RTAUDIO_API_TYPE LINUX_PULSE
-#elif defined(HAVE_ALSA)
-# define __LINUX_ALSA__
-# define RTAUDIO_API_TYPE LINUX_ALSA
+# define RTMIDI_API_TYPE WINDOWS_MM
+#else
+# if defined(HAVE_PULSEAUDIO)
+# define __LINUX_PULSE__
+# define RTAUDIO_API_TYPE LINUX_PULSE
+# elif defined(HAVE_ALSA)
+# define __LINUX_ALSA__
+ # define RTAUDIO_API_TYPE LINUX_ALSA
+# endif
+# ifdef HAVE_ALSA
+# define RTMIDI_API_TYPE LINUX_ALSA
+# endif
#endif
#ifdef RTAUDIO_API_TYPE
-# define Point CorePoint /* fix conflict between DGL and macOS Point name */
# include "rtaudio/RtAudio.h"
-# undef Point
+# include "rtmidi/RtMidi.h"
# include "../../extra/ScopedPointer.hpp"
+# include "../../extra/String.hpp"
using DISTRHO_NAMESPACE::ScopedPointer;
+using DISTRHO_NAMESPACE::String;
struct RtAudioBridge : NativeBridge {
// pointer to RtAudio instance
ScopedPointer handle;
+ bool captureEnabled = false;
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ std::vector midiIns;
+ #endif
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ std::vector midiOuts;
+ #endif
+
+ // caching
+ String name;
+ uint nextBufferSize = 512;
+
+ RtAudioBridge()
+ {
+ #if defined(RTMIDI_API_TYPE) && (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT)
+ midiAvailable = true;
+ #endif
+ }
const char* getVersion() const noexcept
{
@@ -56,48 +82,8 @@ struct RtAudioBridge : NativeBridge {
bool open(const char* const clientName) override
{
- ScopedPointer rtAudio;
-
- try {
- rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE);
- } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false);
-
- uint rtAudioBufferFrames = 512;
-
- #if DISTRHO_PLUGIN_NUM_INPUTS > 0
- RtAudio::StreamParameters inParams;
- inParams.deviceId = rtAudio->getDefaultInputDevice();
- inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS;
- RtAudio::StreamParameters* const inParamsPtr = &inParams;
- #else
- RtAudio::StreamParameters* const inParamsPtr = nullptr;
- #endif
-
- #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
- RtAudio::StreamParameters outParams;
- outParams.deviceId = rtAudio->getDefaultOutputDevice();
- outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS;
- RtAudio::StreamParameters* const outParamsPtr = &outParams;
- #else
- RtAudio::StreamParameters* const outParamsPtr = nullptr;
- #endif
-
- RtAudio::StreamOptions opts;
- opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT;
- opts.streamName = clientName;
-
- try {
- rtAudio->openStream(outParamsPtr, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames,
- RtAudioCallback, this, &opts, nullptr);
- } catch (const RtAudioError& err) {
- d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
- return false;
- } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false);
-
- handle = rtAudio;
- bufferSize = rtAudioBufferFrames;
- sampleRate = handle->getStreamSampleRate();
- return true;
+ name = clientName;
+ return _open(false);
}
bool close() override
@@ -111,6 +97,9 @@ struct RtAudioBridge : NativeBridge {
} DISTRHO_SAFE_EXCEPTION("handle->abortStream()");
}
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ freeBuffers();
+ #endif
handle = nullptr;
return true;
}
@@ -137,6 +126,218 @@ struct RtAudioBridge : NativeBridge {
return true;
}
+ bool isAudioInputEnabled() const override
+ {
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ return captureEnabled;
+ #else
+ return false;
+ #endif
+ }
+
+ bool requestAudioInput() override
+ {
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ // stop audio first
+ deactivate();
+ close();
+
+ // try to open with capture enabled
+ const bool ok = _open(true);
+
+ if (ok)
+ captureEnabled = true;
+ else
+ _open(false);
+
+ activate();
+ return ok;
+ #else
+ return false;
+ #endif
+ }
+
+ bool isMIDIEnabled() const override
+ {
+ d_stdout("%s %d", __PRETTY_FUNCTION__, __LINE__);
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ if (!midiIns.empty())
+ return true;
+ #endif
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ if (!midiOuts.empty())
+ return true;
+ #endif
+ return false;
+ }
+
+ bool requestMIDI() override
+ {
+ d_stdout("%s %d", __PRETTY_FUNCTION__, __LINE__);
+ // clear ports in use first
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ if (!midiIns.empty())
+ {
+ try {
+ midiIns.clear();
+ } catch (const RtMidiError& err) {
+ d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
+ return false;
+ } DISTRHO_SAFE_EXCEPTION_RETURN("midiIns.clear()", false);
+ }
+ #endif
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ if (!midiOuts.size())
+ {
+ try {
+ midiOuts.clear();
+ } catch (const RtMidiError& err) {
+ d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
+ return false;
+ } DISTRHO_SAFE_EXCEPTION_RETURN("midiOuts.clear()", false);
+ }
+ #endif
+
+ // query port count
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ uint midiInCount;
+ try {
+ RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer());
+ midiInCount = midiIn.getPortCount();
+ } catch (const RtMidiError& err) {
+ d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
+ return false;
+ } DISTRHO_SAFE_EXCEPTION_RETURN("midiIn.getPortCount()", false);
+ #endif
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ uint midiOutCount;
+ try {
+ RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer());
+ midiOutCount = midiOut.getPortCount();
+ } catch (const RtMidiError& err) {
+ d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
+ return false;
+ } DISTRHO_SAFE_EXCEPTION_RETURN("midiOut.getPortCount()", false);
+ #endif
+
+ // open all possible ports
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ for (uint i=0; i rtAudio;
+
+ try {
+ rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE);
+ } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false);
+
+ uint rtAudioBufferFrames = nextBufferSize;
+
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ RtAudio::StreamParameters inParams;
+ #endif
+ RtAudio::StreamParameters* inParamsPtr = nullptr;
+
+ #if DISTRHO_PLUGIN_NUM_INPUTS > 0
+ if (withInput)
+ {
+ inParams.deviceId = rtAudio->getDefaultInputDevice();
+ inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS;
+ inParamsPtr = &inParams;
+ }
+ #endif
+
+ #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
+ RtAudio::StreamParameters outParams;
+ outParams.deviceId = rtAudio->getDefaultOutputDevice();
+ outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS;
+ RtAudio::StreamParameters* const outParamsPtr = &outParams;
+ #else
+ RtAudio::StreamParameters* const outParamsPtr = nullptr;
+ #endif
+
+ RtAudio::StreamOptions opts;
+ opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT;
+ opts.streamName = name.buffer();
+
+ try {
+ rtAudio->openStream(outParamsPtr, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames,
+ RtAudioCallback, this, &opts, nullptr);
+ } catch (const RtAudioError& err) {
+ d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
+ return false;
+ } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false);
+
+ handle = rtAudio;
+ bufferSize = rtAudioBufferFrames;
+ sampleRate = handle->getStreamSampleRate();
+ allocBuffers(!withInput, true);
+ return true;
+ }
+
static int RtAudioCallback(void* const outputBuffer,
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
void* const inputBuffer,
@@ -177,6 +378,23 @@ struct RtAudioBridge : NativeBridge {
return 0;
}
+
+ #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ static void RtMidiCallback(double /*timeStamp*/, std::vector* message, void* userData)
+ {
+ const size_t len = message->size();
+ DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= kMaxMIDIInputMessageSize,);
+
+ RtAudioBridge* const self = static_cast(userData);
+
+ // TODO timestamp handling
+ self->midiInBufferPending.writeByte(static_cast(len));
+ self->midiInBufferPending.writeCustomData(message->data(), len);
+ for (uint8_t i=0; imidiInBufferPending.writeByte(0);
+ self->midiInBufferPending.commitWrite();
+ }
+ #endif
};
#endif // RTAUDIO_API_TYPE
diff --git a/dpf/distrho/src/jackbridge/SDL2Bridge.hpp b/dpf/distrho/src/jackbridge/SDL2Bridge.hpp
index 88cfd64..e40093c 100644
--- a/dpf/distrho/src/jackbridge/SDL2Bridge.hpp
+++ b/dpf/distrho/src/jackbridge/SDL2Bridge.hpp
@@ -21,6 +21,14 @@
#include
+#ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME
+# define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
+#endif
+
+#ifndef SDL_HINT_AUDIO_DEVICE_STREAM_NAME
+# define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME"
+#endif
+
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0
# error SDL without audio does not make sense
#endif
@@ -131,7 +139,7 @@ struct SDL2Bridge : NativeBridge {
sampleRate = receivedPlayback.freq;
#endif
- allocBuffers();
+ allocBuffers(true, false);
return true;
}
diff --git a/dpf/distrho/src/jackbridge/WebBridge.hpp b/dpf/distrho/src/jackbridge/WebBridge.hpp
index 71154bb..3afb9e3 100644
--- a/dpf/distrho/src/jackbridge/WebBridge.hpp
+++ b/dpf/distrho/src/jackbridge/WebBridge.hpp
@@ -52,7 +52,7 @@ struct WebBridge : NativeBridge {
return 0;
}) != 0;
#endif
-
+
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
midiAvailable = EM_ASM_INT({
return typeof(navigator.requestMIDIAccess) === 'function' ? 1 : 0;
@@ -111,13 +111,16 @@ struct WebBridge : NativeBridge {
return false;
}
- bufferSize = 2048;
+ bufferSize = EM_ASM_INT({
+ var WAB = Module['WebAudioBridge'];
+ return WAB['minimizeBufferSize'] ? 256 : 2048;
+ });
sampleRate = EM_ASM_INT_V({
var WAB = Module['WebAudioBridge'];
return WAB.audioContext.sampleRate;
});
- allocBuffers();
+ allocBuffers(true, true);
EM_ASM({
var numInputs = $0;
@@ -125,23 +128,28 @@ struct WebBridge : NativeBridge {
var bufferSize = $2;
var WAB = Module['WebAudioBridge'];
+ var realBufferSize = WAB['minimizeBufferSize'] ? 2048 : bufferSize;
+ var divider = realBufferSize / bufferSize;
+
// main processor
- WAB.processor = WAB.audioContext['createScriptProcessor'](bufferSize, numInputs, numOutputs);
+ WAB.processor = WAB.audioContext['createScriptProcessor'](realBufferSize, numInputs, numOutputs);
WAB.processor['onaudioprocess'] = function (e) {
// var timestamp = performance.now();
- for (var i = 0; i < numInputs; ++i) {
- var buffer = e['inputBuffer']['getChannelData'](i);
- for (var j = 0; j < bufferSize; ++j) {
- // setValue($3 + ((bufferSize * i) + j) * 4, buffer[j], 'float');
- HEAPF32[$3 + (((bufferSize * i) + j) << 2) >> 2] = buffer[j];
+ for (var k = 0; k < divider; ++k) {
+ for (var i = 0; i < numInputs; ++i) {
+ var buffer = e['inputBuffer']['getChannelData'](i);
+ for (var j = 0; j < bufferSize; ++j) {
+ // setValue($3 + ((bufferSize * i) + j) * 4, buffer[j], 'float');
+ HEAPF32[$3 + (((bufferSize * i) + j) << 2) >> 2] = buffer[bufferSize * k + j];
+ }
}
- }
- dynCall('vi', $4, [$5]);
- for (var i = 0; i < numOutputs; ++i) {
- var buffer = e['outputBuffer']['getChannelData'](i);
- var offset = bufferSize * (numInputs + i);
- for (var j = 0; j < bufferSize; ++j) {
- buffer[j] = HEAPF32[$3 + ((offset + j) << 2) >> 2];
+ dynCall('vi', $4, [$5]);
+ for (var i = 0; i < numOutputs; ++i) {
+ var buffer = e['outputBuffer']['getChannelData'](i);
+ var offset = bufferSize * (numInputs + i);
+ for (var j = 0; j < bufferSize; ++j) {
+ buffer[bufferSize * k + j] = HEAPF32[$3 + ((offset + j) << 2) >> 2];
+ }
}
}
};
@@ -152,7 +160,6 @@ struct WebBridge : NativeBridge {
// resume/start playback on first click
document.addEventListener('click', function(e) {
var WAB = Module['WebAudioBridge'];
- console.log(WAB.audioContext.state);
if (WAB.audioContext.state === 'suspended')
WAB.audioContext.resume();
});
@@ -279,7 +286,7 @@ struct WebBridge : NativeBridge {
bufferSize = newBufferSize;
freeBuffers();
- allocBuffers();
+ allocBuffers(true, true);
if (bufferSizeCallback != nullptr)
bufferSizeCallback(newBufferSize, jackBufferSizeArg);
diff --git a/dpf/distrho/src/jackbridge/rtmidi/LICENSE b/dpf/distrho/src/jackbridge/rtmidi/LICENSE
new file mode 100644
index 0000000..313fd19
--- /dev/null
+++ b/dpf/distrho/src/jackbridge/rtmidi/LICENSE
@@ -0,0 +1,27 @@
+
+RtMidi: realtime MIDI i/o C++ classes
+Copyright (c) 2003-2021 Gary P. Scavone
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+Any person wishing to distribute modifications to the Software is
+asked to send the modifications to the original developer so that
+they can be incorporated into the canonical version. This is,
+however, not a binding provision of this license.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/dpf/distrho/src/jackbridge/rtmidi/RtMidi.cpp b/dpf/distrho/src/jackbridge/rtmidi/RtMidi.cpp
new file mode 100644
index 0000000..62781f0
--- /dev/null
+++ b/dpf/distrho/src/jackbridge/rtmidi/RtMidi.cpp
@@ -0,0 +1,3930 @@
+/**********************************************************************/
+/*! \class RtMidi
+ \brief An abstract base class for realtime MIDI input/output.
+
+ This class implements some common functionality for the realtime
+ MIDI input/output subclasses RtMidiIn and RtMidiOut.
+
+ RtMidi GitHub site: https://github.com/thestk/rtmidi
+ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/
+
+ RtMidi: realtime MIDI i/o C++ classes
+ Copyright (c) 2003-2021 Gary P. Scavone
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ Any person wishing to distribute modifications to the Software is
+ asked to send the modifications to the original developer so that
+ they can be incorporated into the canonical version. This is,
+ however, not a binding provision of this license.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**********************************************************************/
+
+#include "RtMidi.h"
+#include
+#if defined(__APPLE__)
+#include
+#endif
+
+#if (TARGET_OS_IPHONE == 1)
+
+ #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
+ #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos
+
+ #include
+ class CTime2nsFactor
+ {
+ public:
+ CTime2nsFactor()
+ {
+ mach_timebase_info_data_t tinfo;
+ mach_timebase_info(&tinfo);
+ Factor = (double)tinfo.numer / tinfo.denom;
+ }
+ static double Factor;
+ };
+ double CTime2nsFactor::Factor;
+ static CTime2nsFactor InitTime2nsFactor;
+ #undef AudioGetCurrentHostTime
+ #undef AudioConvertHostTimeToNanos
+ #define AudioGetCurrentHostTime (uint64_t) mach_absolute_time
+ #define AudioConvertHostTimeToNanos(t) t *CTime2nsFactor::Factor
+ #define EndianS32_BtoN(n) n
+
+#endif
+
+// Default for Windows is to add an identifier to the port names; this
+// flag can be defined (e.g. in your project file) to disable this behaviour.
+//#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
+
+// **************************************************************** //
+//
+// MidiInApi and MidiOutApi subclass prototypes.
+//
+// **************************************************************** //
+
+#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__)
+ #define __RTMIDI_DUMMY__
+#endif
+
+#if defined(__MACOSX_CORE__)
+#include
+
+class MidiInCore: public MidiInApi
+{
+ public:
+ MidiInCore( const std::string &clientName, unsigned int queueSizeLimit );
+ ~MidiInCore( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+
+ protected:
+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw();
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutCore: public MidiOutApi
+{
+ public:
+ MidiOutCore( const std::string &clientName );
+ ~MidiOutCore( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+ void sendMessage( const unsigned char *message, size_t size );
+
+ protected:
+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw();
+ void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__UNIX_JACK__)
+
+class MidiInJack: public MidiInApi
+{
+ public:
+ MidiInJack( const std::string &clientName, unsigned int queueSizeLimit );
+ ~MidiInJack( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName);
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+
+ protected:
+ std::string clientName;
+
+ void connect( void );
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutJack: public MidiOutApi
+{
+ public:
+ MidiOutJack( const std::string &clientName );
+ ~MidiOutJack( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName);
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+ void sendMessage( const unsigned char *message, size_t size );
+
+ protected:
+ std::string clientName;
+
+ void connect( void );
+ void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__LINUX_ALSA__)
+
+class MidiInAlsa: public MidiInApi
+{
+ public:
+ MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit );
+ ~MidiInAlsa( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName);
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutAlsa: public MidiOutApi
+{
+ public:
+ MidiOutAlsa( const std::string &clientName );
+ ~MidiOutAlsa( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+ void sendMessage( const unsigned char *message, size_t size );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__WINDOWS_MM__)
+
+class MidiInWinMM: public MidiInApi
+{
+ public:
+ MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit );
+ ~MidiInWinMM( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutWinMM: public MidiOutApi
+{
+ public:
+ MidiOutWinMM( const std::string &clientName );
+ ~MidiOutWinMM( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+ void sendMessage( const unsigned char *message, size_t size );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__WEB_MIDI_API__)
+
+class MidiInWeb : public MidiInApi
+{
+ std::string client_name{};
+ std::string web_midi_id{};
+ int open_port_number{-1};
+
+ public:
+ MidiInWeb(const std::string &/*clientName*/, unsigned int queueSizeLimit );
+ ~MidiInWeb( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+
+ void onMidiMessage( uint8_t* data, double domHishResTimeStamp );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutWeb: public MidiOutApi
+{
+ std::string client_name{};
+ std::string web_midi_id{};
+ int open_port_number{-1};
+
+ public:
+ MidiOutWeb( const std::string &clientName );
+ ~MidiOutWeb( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };
+ void openPort( unsigned int portNumber, const std::string &portName );
+ void openVirtualPort( const std::string &portName );
+ void closePort( void );
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+ unsigned int getPortCount( void );
+ std::string getPortName( unsigned int portNumber );
+ void sendMessage( const unsigned char *message, size_t size );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__RTMIDI_DUMMY__)
+
+class MidiInDummy: public MidiInApi
+{
+ public:
+ MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+ void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {}
+ void openVirtualPort( const std::string &/*portName*/ ) {}
+ void closePort( void ) {}
+ void setClientName( const std::string &/*clientName*/ ) {};
+ void setPortName( const std::string &/*portName*/ ) {};
+ unsigned int getPortCount( void ) { return 0; }
+ std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
+
+ protected:
+ void initialize( const std::string& /*clientName*/ ) {}
+};
+
+class MidiOutDummy: public MidiOutApi
+{
+ public:
+ MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+ void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {}
+ void openVirtualPort( const std::string &/*portName*/ ) {}
+ void closePort( void ) {}
+ void setClientName( const std::string &/*clientName*/ ) {};
+ void setPortName( const std::string &/*portName*/ ) {};
+ unsigned int getPortCount( void ) { return 0; }
+ std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
+ void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {}
+
+ protected:
+ void initialize( const std::string& /*clientName*/ ) {}
+};
+
+#endif
+
+//*********************************************************************//
+// RtMidi Definitions
+//*********************************************************************//
+
+RtMidi :: RtMidi()
+ : rtapi_(0)
+{
+}
+
+RtMidi :: ~RtMidi()
+{
+ delete rtapi_;
+ rtapi_ = 0;
+}
+
+RtMidi::RtMidi(RtMidi&& other) noexcept {
+ rtapi_ = other.rtapi_;
+ other.rtapi_ = nullptr;
+}
+
+std::string RtMidi :: getVersion( void ) throw()
+{
+ return std::string( RTMIDI_VERSION );
+}
+
+// Define API names and display names.
+// Must be in same order as API enum.
+extern "C" {
+const char* rtmidi_api_names[][2] = {
+ { "unspecified" , "Unknown" },
+ { "core" , "CoreMidi" },
+ { "alsa" , "ALSA" },
+ { "jack" , "Jack" },
+ { "winmm" , "Windows MultiMedia" },
+ { "web" , "Web MIDI API" },
+ { "dummy" , "Dummy" },
+};
+const unsigned int rtmidi_num_api_names =
+ sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]);
+
+// The order here will control the order of RtMidi's API search in
+// the constructor.
+extern "C" const RtMidi::Api rtmidi_compiled_apis[] = {
+#if defined(__MACOSX_CORE__)
+ RtMidi::MACOSX_CORE,
+#endif
+#if defined(__LINUX_ALSA__)
+ RtMidi::LINUX_ALSA,
+#endif
+#if defined(__UNIX_JACK__)
+ RtMidi::UNIX_JACK,
+#endif
+#if defined(__WINDOWS_MM__)
+ RtMidi::WINDOWS_MM,
+#endif
+#if defined(__WEB_MIDI_API__)
+ RtMidi::WEB_MIDI_API,
+#endif
+#if defined(__RTMIDI_DUMMY__)
+ RtMidi::RTMIDI_DUMMY,
+#endif
+ RtMidi::UNSPECIFIED,
+};
+extern "C" const unsigned int rtmidi_num_compiled_apis =
+ sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1;
+}
+
+void RtMidi :: getCompiledApi( std::vector &apis ) throw()
+{
+ apis = std::vector(rtmidi_compiled_apis,
+ rtmidi_compiled_apis + rtmidi_num_compiled_apis);
+}
+
+std::string RtMidi :: getApiName( RtMidi::Api api )
+{
+ if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS)
+ return "";
+ return rtmidi_api_names[api][0];
+}
+
+std::string RtMidi :: getApiDisplayName( RtMidi::Api api )
+{
+ if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS)
+ return "Unknown";
+ return rtmidi_api_names[api][1];
+}
+
+RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name )
+{
+ unsigned int i=0;
+ for (i = 0; i < rtmidi_num_compiled_apis; ++i)
+ if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0])
+ return rtmidi_compiled_apis[i];
+ return RtMidi::UNSPECIFIED;
+}
+
+void RtMidi :: setClientName( const std::string &clientName )
+{
+ rtapi_->setClientName( clientName );
+}
+
+void RtMidi :: setPortName( const std::string &portName )
+{
+ rtapi_->setPortName( portName );
+}
+
+
+//*********************************************************************//
+// RtMidiIn Definitions
+//*********************************************************************//
+
+void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit )
+{
+ delete rtapi_;
+ rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+ if ( api == UNIX_JACK )
+ rtapi_ = new MidiInJack( clientName, queueSizeLimit );
+#endif
+#if defined(__LINUX_ALSA__)
+ if ( api == LINUX_ALSA )
+ rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );
+#endif
+#if defined(__WINDOWS_MM__)
+ if ( api == WINDOWS_MM )
+ rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );
+#endif
+#if defined(__MACOSX_CORE__)
+ if ( api == MACOSX_CORE )
+ rtapi_ = new MidiInCore( clientName, queueSizeLimit );
+#endif
+#if defined(__WEB_MIDI_API__)
+ if ( api == WEB_MIDI_API )
+ rtapi_ = new MidiInWeb( clientName, queueSizeLimit );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+ if ( api == RTMIDI_DUMMY )
+ rtapi_ = new MidiInDummy( clientName, queueSizeLimit );
+#endif
+}
+
+RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit )
+ : RtMidi()
+{
+ if ( api != UNSPECIFIED ) {
+ // Attempt to open the specified API.
+ openMidiApi( api, clientName, queueSizeLimit );
+ if ( rtapi_ ) return;
+
+ // No compiled support for specified API value. Issue a warning
+ // and continue as if no API was specified.
+ std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;
+ }
+
+ // Iterate through the compiled APIs and return as soon as we find
+ // one with at least one port or we reach the end of the list.
+ std::vector< RtMidi::Api > apis;
+ getCompiledApi( apis );
+ for ( unsigned int i=0; igetPortCount() ) break;
+ }
+
+ if ( rtapi_ ) return;
+
+ // It should not be possible to get here because the preprocessor
+ // definition __RTMIDI_DUMMY__ is automatically defined if no
+ // API-specific definitions are passed to the compiler. But just in
+ // case something weird happens, we'll throw an error.
+ std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";
+ throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiIn :: ~RtMidiIn() throw()
+{
+}
+
+
+//*********************************************************************//
+// RtMidiOut Definitions
+//*********************************************************************//
+
+void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName )
+{
+ delete rtapi_;
+ rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+ if ( api == UNIX_JACK )
+ rtapi_ = new MidiOutJack( clientName );
+#endif
+#if defined(__LINUX_ALSA__)
+ if ( api == LINUX_ALSA )
+ rtapi_ = new MidiOutAlsa( clientName );
+#endif
+#if defined(__WINDOWS_MM__)
+ if ( api == WINDOWS_MM )
+ rtapi_ = new MidiOutWinMM( clientName );
+#endif
+#if defined(__MACOSX_CORE__)
+ if ( api == MACOSX_CORE )
+ rtapi_ = new MidiOutCore( clientName );
+#endif
+#if defined(__WEB_MIDI_API__)
+ if ( api == WEB_MIDI_API )
+ rtapi_ = new MidiOutWeb( clientName );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+ if ( api == RTMIDI_DUMMY )
+ rtapi_ = new MidiOutDummy( clientName );
+#endif
+}
+
+RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName)
+{
+ if ( api != UNSPECIFIED ) {
+ // Attempt to open the specified API.
+ openMidiApi( api, clientName );
+ if ( rtapi_ ) return;
+
+ // No compiled support for specified API value. Issue a warning
+ // and continue as if no API was specified.
+ std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;
+ }
+
+ // Iterate through the compiled APIs and return as soon as we find
+ // one with at least one port or we reach the end of the list.
+ std::vector< RtMidi::Api > apis;
+ getCompiledApi( apis );
+ for ( unsigned int i=0; igetPortCount() ) break;
+ }
+
+ if ( rtapi_ ) return;
+
+ // It should not be possible to get here because the preprocessor
+ // definition __RTMIDI_DUMMY__ is automatically defined if no
+ // API-specific definitions are passed to the compiler. But just in
+ // case something weird happens, we'll thrown an error.
+ std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";
+ throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiOut :: ~RtMidiOut() throw()
+{
+}
+
+//*********************************************************************//
+// Common MidiApi Definitions
+//*********************************************************************//
+
+MidiApi :: MidiApi( void )
+ : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0)
+{
+}
+
+MidiApi :: ~MidiApi( void )
+{
+}
+
+void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )
+{
+ errorCallback_ = errorCallback;
+ errorCallbackUserData_ = userData;
+}
+
+void MidiApi :: error( RtMidiError::Type type, std::string errorString )
+{
+ if ( errorCallback_ ) {
+
+ if ( firstErrorOccurred_ )
+ return;
+
+ firstErrorOccurred_ = true;
+ const std::string errorMessage = errorString;
+
+ errorCallback_( type, errorMessage, errorCallbackUserData_ );
+ firstErrorOccurred_ = false;
+ return;
+ }
+
+ if ( type == RtMidiError::WARNING ) {
+ std::cerr << '\n' << errorString << "\n\n";
+ }
+ else if ( type == RtMidiError::DEBUG_WARNING ) {
+#if defined(__RTMIDI_DEBUG__)
+ std::cerr << '\n' << errorString << "\n\n";
+#endif
+ }
+ else {
+ std::cerr << '\n' << errorString << "\n\n";
+ throw RtMidiError( errorString, type );
+ }
+}
+
+//*********************************************************************//
+// Common MidiInApi Definitions
+//*********************************************************************//
+
+MidiInApi :: MidiInApi( unsigned int queueSizeLimit )
+ : MidiApi()
+{
+ // Allocate the MIDI queue.
+ inputData_.queue.ringSize = queueSizeLimit;
+ if ( inputData_.queue.ringSize > 0 )
+ inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];
+}
+
+MidiInApi :: ~MidiInApi( void )
+{
+ // Delete the MIDI queue.
+ if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;
+}
+
+void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )
+{
+ if ( inputData_.usingCallback ) {
+ errorString_ = "MidiInApi::setCallback: a callback function is already set!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ if ( !callback ) {
+ errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ inputData_.userCallback = callback;
+ inputData_.userData = userData;
+ inputData_.usingCallback = true;
+}
+
+void MidiInApi :: cancelCallback()
+{
+ if ( !inputData_.usingCallback ) {
+ errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ inputData_.userCallback = 0;
+ inputData_.userData = 0;
+ inputData_.usingCallback = false;
+}
+
+void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
+{
+ inputData_.ignoreFlags = 0;
+ if ( midiSysex ) inputData_.ignoreFlags = 0x01;
+ if ( midiTime ) inputData_.ignoreFlags |= 0x02;
+ if ( midiSense ) inputData_.ignoreFlags |= 0x04;
+}
+
+double MidiInApi :: getMessage( std::vector *message )
+{
+ message->clear();
+
+ if ( inputData_.usingCallback ) {
+ errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
+ error( RtMidiError::WARNING, errorString_ );
+ return 0.0;
+ }
+
+ double timeStamp;
+ if ( !inputData_.queue.pop( message, &timeStamp ) )
+ return 0.0;
+
+ return timeStamp;
+}
+
+void MidiInApi :: setBufferSize( unsigned int size, unsigned int count )
+{
+ inputData_.bufferSize = size;
+ inputData_.bufferCount = count;
+}
+
+unsigned int MidiInApi::MidiQueue::size( unsigned int *__back,
+ unsigned int *__front )
+{
+ // Access back/front members exactly once and make stack copies for
+ // size calculation
+ unsigned int _back = back, _front = front, _size;
+ if ( _back >= _front )
+ _size = _back - _front;
+ else
+ _size = ringSize - _front + _back;
+
+ // Return copies of back/front so no new and unsynchronized accesses
+ // to member variables are needed.
+ if ( __back ) *__back = _back;
+ if ( __front ) *__front = _front;
+ return _size;
+}
+
+// As long as we haven't reached our queue size limit, push the message.
+bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg )
+{
+ // Local stack copies of front/back
+ unsigned int _back, _front, _size;
+
+ // Get back/front indexes exactly once and calculate current size
+ _size = size( &_back, &_front );
+
+ if ( _size < ringSize-1 )
+ {
+ ring[_back] = msg;
+ back = (back+1)%ringSize;
+ return true;
+ }
+
+ return false;
+}
+
+bool MidiInApi::MidiQueue::pop( std::vector *msg, double* timeStamp )
+{
+ // Local stack copies of front/back
+ unsigned int _back, _front, _size;
+
+ // Get back/front indexes exactly once and calculate current size
+ _size = size( &_back, &_front );
+
+ if ( _size == 0 )
+ return false;
+
+ // Copy queued message to the vector pointer argument and then "pop" it.
+ msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() );
+ *timeStamp = ring[_front].timeStamp;
+
+ // Update front
+ front = (front+1)%ringSize;
+ return true;
+}
+
+//*********************************************************************//
+// Common MidiOutApi Definitions
+//*********************************************************************//
+
+MidiOutApi :: MidiOutApi( void )
+ : MidiApi()
+{
+}
+
+MidiOutApi :: ~MidiOutApi( void )
+{
+}
+
+// *************************************************** //
+//
+// OS/API-specific methods.
+//
+// *************************************************** //
+
+#if defined(__MACOSX_CORE__)
+
+// The CoreMIDI API is based on the use of a callback function for
+// MIDI input. We convert the system specific time stamps to delta
+// time values.
+
+// These are not available on iOS.
+#if (TARGET_OS_IPHONE == 0)
+ #include
+ #include
+#endif
+
+// A structure to hold variables related to the CoreMIDI API
+// implementation.
+struct CoreMidiData {
+ MIDIClientRef client;
+ MIDIPortRef port;
+ MIDIEndpointRef endpoint;
+ MIDIEndpointRef destinationId;
+ unsigned long long lastTime;
+ MIDISysexSendRequest sysexreq;
+};
+
+static MIDIClientRef CoreMidiClientSingleton = 0;
+
+void RtMidi_setCoreMidiClientSingleton(MIDIClientRef client){
+ CoreMidiClientSingleton = client;
+}
+
+void RtMidi_disposeCoreMidiClientSingleton(){
+ if (CoreMidiClientSingleton == 0){
+ return;
+ }
+ MIDIClientDispose( CoreMidiClientSingleton );
+ CoreMidiClientSingleton = 0;
+}
+
+//*********************************************************************//
+// API: OS-X
+// Class Definitions: MidiInCore
+//*********************************************************************//
+
+static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )
+{
+ MidiInApi::RtMidiInData *data = static_cast (procRef);
+ CoreMidiData *apiData = static_cast (data->apiData);
+
+ unsigned char status;
+ unsigned short nBytes, iByte, size;
+ unsigned long long time;
+
+ bool& continueSysex = data->continueSysex;
+ MidiInApi::MidiMessage& message = data->message;
+
+ const MIDIPacket *packet = &list->packet[0];
+ for ( unsigned int i=0; inumPackets; ++i ) {
+
+ // My interpretation of the CoreMIDI documentation: all message
+ // types, except sysex, are complete within a packet and there may
+ // be several of them in a single packet. Sysex messages can be
+ // broken across multiple packets and PacketLists but are bundled
+ // alone within each packet (these packets do not contain other
+ // message types). If sysex messages are split across multiple
+ // MIDIPacketLists, they must be handled by multiple calls to this
+ // function.
+
+ nBytes = packet->length;
+ if ( nBytes == 0 ) {
+ packet = MIDIPacketNext( packet );
+ continue;
+ }
+
+ // Calculate time stamp.
+ if ( data->firstMessage ) {
+ message.timeStamp = 0.0;
+ data->firstMessage = false;
+ }
+ else {
+ time = packet->timeStamp;
+ if ( time == 0 ) { // this happens when receiving asynchronous sysex messages
+ time = AudioGetCurrentHostTime();
+ }
+ time -= apiData->lastTime;
+ time = AudioConvertHostTimeToNanos( time );
+ if ( !continueSysex )
+ message.timeStamp = time * 0.000000001;
+ }
+
+ // Track whether any non-filtered messages were found in this
+ // packet for timestamp calculation
+ bool foundNonFiltered = false;
+
+ iByte = 0;
+ if ( continueSysex ) {
+ // We have a continuing, segmented sysex message.
+ if ( !( data->ignoreFlags & 0x01 ) ) {
+ // If we're not ignoring sysex messages, copy the entire packet.
+ for ( unsigned int j=0; jdata[j] );
+ }
+ continueSysex = packet->data[nBytes-1] != 0xF7;
+
+ if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {
+ // If not a continuing sysex message, invoke the user callback function or queue the message.
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( message.timeStamp, &message.bytes, data->userData );
+ }
+ else {
+ // As long as we haven't reached our queue size limit, push the message.
+ if ( !data->queue.push( message ) )
+ std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
+ }
+ message.bytes.clear();
+ }
+ }
+ else {
+ while ( iByte < nBytes ) {
+ size = 0;
+ // We are expecting that the next byte in the packet is a status byte.
+ status = packet->data[iByte];
+ if ( !(status & 0x80) ) break;
+ // Determine the number of bytes in the MIDI message.
+ if ( status < 0xC0 ) size = 3;
+ else if ( status < 0xE0 ) size = 2;
+ else if ( status < 0xF0 ) size = 3;
+ else if ( status == 0xF0 ) {
+ // A MIDI sysex
+ if ( data->ignoreFlags & 0x01 ) {
+ size = 0;
+ iByte = nBytes;
+ }
+ else size = nBytes - iByte;
+ continueSysex = packet->data[nBytes-1] != 0xF7;
+ }
+ else if ( status == 0xF1 ) {
+ // A MIDI time code message
+ if ( data->ignoreFlags & 0x02 ) {
+ size = 0;
+ iByte += 2;
+ }
+ else size = 2;
+ }
+ else if ( status == 0xF2 ) size = 3;
+ else if ( status == 0xF3 ) size = 2;
+ else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {
+ // A MIDI timing tick message and we're ignoring it.
+ size = 0;
+ iByte += 1;
+ }
+ else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {
+ // A MIDI active sensing message and we're ignoring it.
+ size = 0;
+ iByte += 1;
+ }
+ else size = 1;
+
+ // Copy the MIDI data to our vector.
+ if ( size ) {
+ foundNonFiltered = true;
+ message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
+ if ( !continueSysex ) {
+ // If not a continuing sysex message, invoke the user callback function or queue the message.
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( message.timeStamp, &message.bytes, data->userData );
+ }
+ else {
+ // As long as we haven't reached our queue size limit, push the message.
+ if ( !data->queue.push( message ) )
+ std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
+ }
+ message.bytes.clear();
+ }
+ iByte += size;
+ }
+ }
+ }
+
+ // Save the time of the last non-filtered message
+ if ( foundNonFiltered ) {
+ apiData->lastTime = packet->timeStamp;
+ if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages
+ apiData->lastTime = AudioGetCurrentHostTime();
+ }
+ }
+
+ packet = MIDIPacketNext(packet);
+ }
+}
+
+MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ MidiInCore::initialize( clientName );
+}
+
+MidiInCore :: ~MidiInCore( void )
+{
+ // Close a connection if it exists.
+ MidiInCore::closePort();
+
+ // Cleanup.
+ CoreMidiData *data = static_cast (apiData_);
+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+ delete data;
+}
+
+MIDIClientRef MidiInCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {
+
+ if (CoreMidiClientSingleton == 0){
+ // Set up our client.
+ MIDIClientRef client;
+
+ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
+ if ( result != noErr ) {
+ std::ostringstream ost;
+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";
+ errorString_ = ost.str();
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return 0;
+ }
+ CFRelease( name );
+
+ CoreMidiClientSingleton = client;
+ }
+
+ return CoreMidiClientSingleton;
+}
+
+void MidiInCore :: initialize( const std::string& clientName )
+{
+ // Set up our client.
+ MIDIClientRef client = getCoreMidiClientSingleton(clientName);
+
+ // Save our api-specific connection information.
+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+ data->client = client;
+ data->endpoint = 0;
+ apiData_ = (void *) data;
+ inputData_.apiData = (void *) data;
+}
+
+void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiInCore::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+ unsigned int nSrc = MIDIGetNumberOfSources();
+ if ( nSrc < 1 ) {
+ errorString_ = "MidiInCore::openPort: no MIDI input sources found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ if ( portNumber >= nSrc ) {
+ std::ostringstream ost;
+ ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ MIDIPortRef port;
+ CoreMidiData *data = static_cast (apiData_);
+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDIInputPortCreate( data->client,
+ portNameRef,
+ midiInputCallback, (void *)&inputData_, &port );
+ CFRelease( portNameRef );
+
+ if ( result != noErr ) {
+ errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Get the desired input source identifier.
+ MIDIEndpointRef endpoint = MIDIGetSource( portNumber );
+ if ( endpoint == 0 ) {
+ MIDIPortDispose( port );
+ errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Make the connection.
+ result = MIDIPortConnectSource( port, endpoint, NULL );
+ if ( result != noErr ) {
+ MIDIPortDispose( port );
+ errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Save our api-specific port information.
+ data->port = port;
+
+ connected_ = true;
+}
+
+void MidiInCore :: openVirtualPort( const std::string &portName )
+{
+ CoreMidiData *data = static_cast (apiData_);
+
+ // Create a virtual MIDI input destination.
+ MIDIEndpointRef endpoint;
+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDIDestinationCreate( data->client,
+ portNameRef,
+ midiInputCallback, (void *)&inputData_, &endpoint );
+ CFRelease( portNameRef );
+
+ if ( result != noErr ) {
+ errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Save our api-specific connection information.
+ data->endpoint = endpoint;
+}
+
+void MidiInCore :: closePort( void )
+{
+ CoreMidiData *data = static_cast (apiData_);
+
+ if ( data->endpoint ) {
+ MIDIEndpointDispose( data->endpoint );
+ data->endpoint = 0;
+ }
+
+ if ( data->port ) {
+ MIDIPortDispose( data->port );
+ data->port = 0;
+ }
+
+ connected_ = false;
+}
+
+void MidiInCore :: setClientName ( const std::string& )
+{
+
+ errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiInCore :: setPortName ( const std::string& )
+{
+
+ errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiInCore :: getPortCount()
+{
+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+ return MIDIGetNumberOfSources();
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal )
+{
+ CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+ CFStringRef str;
+
+ // Begin with the endpoint's name.
+ str = NULL;
+ MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str );
+ if ( str != NULL ) {
+ CFStringAppend( result, str );
+ CFRelease( str );
+ }
+
+ // some MIDI devices have a leading space in endpoint name. trim
+ CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8);
+ CFStringTrim(result, space);
+ CFRelease(space);
+
+ MIDIEntityRef entity = 0;
+ MIDIEndpointGetEntity( endpoint, &entity );
+ if ( entity == 0 )
+ // probably virtual
+ return result;
+
+ if ( CFStringGetLength( result ) == 0 ) {
+ // endpoint name has zero length -- try the entity
+ str = NULL;
+ MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str );
+ if ( str != NULL ) {
+ CFStringAppend( result, str );
+ CFRelease( str );
+ }
+ }
+ // now consider the device's name
+ MIDIDeviceRef device = 0;
+ MIDIEntityGetDevice( entity, &device );
+ if ( device == 0 )
+ return result;
+
+ str = NULL;
+ MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str );
+ if ( CFStringGetLength( result ) == 0 ) {
+ CFRelease( result );
+ return str;
+ }
+ if ( str != NULL ) {
+ // if an external device has only one entity, throw away
+ // the endpoint name and just use the device name
+ if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) {
+ CFRelease( result );
+ return str;
+ } else {
+ if ( CFStringGetLength( str ) == 0 ) {
+ CFRelease( str );
+ return result;
+ }
+ // does the entity name already start with the device name?
+ // (some drivers do this though they shouldn't)
+ // if so, do not prepend
+ if ( CFStringCompareWithOptions( result, /* endpoint name */
+ str /* device name */,
+ CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) {
+ // prepend the device name to the entity name
+ if ( CFStringGetLength( result ) > 0 )
+ CFStringInsert( result, 0, CFSTR(" ") );
+
+ CFStringInsert( result, 0, str );
+ }
+ CFRelease( str );
+ }
+ }
+ return result;
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint )
+{
+ CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+ CFStringRef str;
+ OSStatus err;
+ int i;
+
+ // Does the endpoint have connections?
+ CFDataRef connections = NULL;
+ int nConnected = 0;
+ bool anyStrings = false;
+ err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections );
+ if ( connections != NULL ) {
+ // It has connections, follow them
+ // Concatenate the names of all connected devices
+ nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID);
+ if ( nConnected ) {
+ const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
+ for ( i=0; i= MIDIGetNumberOfSources() ) {
+ std::ostringstream ost;
+ ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+ }
+
+ portRef = MIDIGetSource( portNumber );
+ nameRef = ConnectedEndpointName( portRef );
+ CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 );
+ CFRelease( nameRef );
+
+ return stringName = name;
+}
+
+//*********************************************************************//
+// API: OS-X
+// Class Definitions: MidiOutCore
+//*********************************************************************//
+
+MidiOutCore :: MidiOutCore( const std::string &clientName )
+ : MidiOutApi()
+{
+ MidiOutCore::initialize( clientName );
+}
+
+MidiOutCore :: ~MidiOutCore( void )
+{
+ // Close a connection if it exists.
+ MidiOutCore::closePort();
+
+ // Cleanup.
+ CoreMidiData *data = static_cast (apiData_);
+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+ delete data;
+}
+
+MIDIClientRef MidiOutCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {
+
+ if (CoreMidiClientSingleton == 0){
+ // Set up our client.
+ MIDIClientRef client;
+
+ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
+ if ( result != noErr ) {
+ std::ostringstream ost;
+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";
+ errorString_ = ost.str();
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return 0;
+ }
+ CFRelease( name );
+
+ CoreMidiClientSingleton = client;
+ }
+
+ return CoreMidiClientSingleton;
+}
+
+void MidiOutCore :: initialize( const std::string& clientName )
+{
+ // Set up our client.
+ MIDIClientRef client = getCoreMidiClientSingleton(clientName);
+
+ // Save our api-specific connection information.
+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+ data->client = client;
+ data->endpoint = 0;
+ apiData_ = (void *) data;
+}
+
+unsigned int MidiOutCore :: getPortCount()
+{
+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+ return MIDIGetNumberOfDestinations();
+}
+
+std::string MidiOutCore :: getPortName( unsigned int portNumber )
+{
+ CFStringRef nameRef;
+ MIDIEndpointRef portRef;
+ char name[128];
+
+ std::string stringName;
+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+ if ( portNumber >= MIDIGetNumberOfDestinations() ) {
+ std::ostringstream ost;
+ ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+ }
+
+ portRef = MIDIGetDestination( portNumber );
+ nameRef = ConnectedEndpointName(portRef);
+ CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 );
+ CFRelease( nameRef );
+
+ return stringName = name;
+}
+
+void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiOutCore::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+ unsigned int nDest = MIDIGetNumberOfDestinations();
+ if (nDest < 1) {
+ errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ if ( portNumber >= nDest ) {
+ std::ostringstream ost;
+ ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ MIDIPortRef port;
+ CoreMidiData *data = static_cast (apiData_);
+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port );
+ CFRelease( portNameRef );
+ if ( result != noErr ) {
+ errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Get the desired output port identifier.
+ MIDIEndpointRef destination = MIDIGetDestination( portNumber );
+ if ( destination == 0 ) {
+ MIDIPortDispose( port );
+ errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Save our api-specific connection information.
+ data->port = port;
+ data->destinationId = destination;
+ connected_ = true;
+}
+
+void MidiOutCore :: closePort( void )
+{
+ CoreMidiData *data = static_cast (apiData_);
+
+ if ( data->endpoint ) {
+ MIDIEndpointDispose( data->endpoint );
+ data->endpoint = 0;
+ }
+
+ if ( data->port ) {
+ MIDIPortDispose( data->port );
+ data->port = 0;
+ }
+
+ connected_ = false;
+}
+
+void MidiOutCore :: setClientName ( const std::string& )
+{
+
+ errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutCore :: setPortName ( const std::string& )
+{
+
+ errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutCore :: openVirtualPort( const std::string &portName )
+{
+ CoreMidiData *data = static_cast (apiData_);
+
+ if ( data->endpoint ) {
+ errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ // Create a virtual MIDI output source.
+ MIDIEndpointRef endpoint;
+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );
+ OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint );
+ CFRelease( portNameRef );
+
+ if ( result != noErr ) {
+ errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Save our api-specific connection information.
+ data->endpoint = endpoint;
+}
+
+void MidiOutCore :: sendMessage( const unsigned char *message, size_t size )
+{
+ // We use the MIDISendSysex() function to asynchronously send sysex
+ // messages. Otherwise, we use a single CoreMidi MIDIPacket.
+ unsigned int nBytes = static_cast (size);
+ if ( nBytes == 0 ) {
+ errorString_ = "MidiOutCore::sendMessage: no data in message argument!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ if ( message[0] != 0xF0 && nBytes > 3 ) {
+ errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
+ CoreMidiData *data = static_cast (apiData_);
+ OSStatus result;
+
+ ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;
+ Byte buffer[bufsize+16]; // pad for other struct members
+ ByteCount listSize = sizeof( buffer );
+ MIDIPacketList *packetList = (MIDIPacketList*)buffer;
+
+ ByteCount remainingBytes = nBytes;
+ while ( remainingBytes ) {
+ MIDIPacket *packet = MIDIPacketListInit( packetList );
+ // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,
+ // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one
+ // MIDIPacket. Here, we reuse the memory allocated above on the stack for all.
+ ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes;
+ const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes];
+ packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr );
+ remainingBytes -= bytesForPacket;
+
+ if ( !packet ) {
+ errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Send to any destinations that may have connected to us.
+ if ( data->endpoint ) {
+ result = MIDIReceived( data->endpoint, packetList );
+ if ( result != noErr ) {
+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
+ error( RtMidiError::WARNING, errorString_ );
+ }
+ }
+
+ // And send to an explicit destination port if we're connected.
+ if ( connected_ ) {
+ result = MIDISend( data->port, data->destinationId, packetList );
+ if ( result != noErr ) {
+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";
+ error( RtMidiError::WARNING, errorString_ );
+ }
+ }
+ }
+}
+
+#endif // __MACOSX_CORE__
+
+
+//*********************************************************************//
+// API: LINUX ALSA SEQUENCER
+//*********************************************************************//
+
+// API information found at:
+// - http://www.alsa-project.org/documentation.php#Library
+
+#if defined(__LINUX_ALSA__)
+
+// The ALSA Sequencer API is based on the use of a callback function for
+// MIDI input.
+//
+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
+// time stamps and other assorted fixes!!!
+
+// If you don't need timestamping for incoming MIDI events, define the
+// preprocessor definition AVOID_TIMESTAMPING to save resources
+// associated with the ALSA sequencer queues.
+
+#include
+#include
+
+// ALSA header file.
+#include
+
+// A structure to hold variables related to the ALSA API
+// implementation.
+struct AlsaMidiData {
+ snd_seq_t *seq;
+ unsigned int portNum;
+ int vport;
+ snd_seq_port_subscribe_t *subscription;
+ snd_midi_event_t *coder;
+ unsigned int bufferSize;
+ unsigned int requestedBufferSize;
+ unsigned char *buffer;
+ pthread_t thread;
+ pthread_t dummy_thread_id;
+ snd_seq_real_time_t lastTime;
+ int queue_id; // an input queue is needed to get timestamped events
+ int trigger_fds[2];
+};
+
+#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
+
+//*********************************************************************//
+// API: LINUX ALSA
+// Class Definitions: MidiInAlsa
+//*********************************************************************//
+
+static void *alsaMidiHandler( void *ptr )
+{
+ MidiInApi::RtMidiInData *data = static_cast (ptr);
+ AlsaMidiData *apiData = static_cast (data->apiData);
+
+ long nBytes;
+ double time;
+ bool continueSysex = false;
+ bool doDecode = false;
+ MidiInApi::MidiMessage message;
+ int poll_fd_count;
+ struct pollfd *poll_fds;
+
+ snd_seq_event_t *ev;
+ int result;
+ result = snd_midi_event_new( 0, &apiData->coder );
+ if ( result < 0 ) {
+ data->doInput = false;
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";
+ return 0;
+ }
+ unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
+ if ( buffer == NULL ) {
+ data->doInput = false;
+ snd_midi_event_free( apiData->coder );
+ apiData->coder = 0;
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";
+ return 0;
+ }
+ snd_midi_event_init( apiData->coder );
+ snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
+
+ poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;
+ poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));
+ snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );
+ poll_fds[0].fd = apiData->trigger_fds[0];
+ poll_fds[0].events = POLLIN;
+
+ while ( data->doInput ) {
+
+ if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
+ // No data pending
+ if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {
+ if ( poll_fds[0].revents & POLLIN ) {
+ bool dummy;
+ int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );
+ (void) res;
+ }
+ }
+ continue;
+ }
+
+ // If here, there should be data.
+ result = snd_seq_event_input( apiData->seq, &ev );
+ if ( result == -ENOSPC ) {
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";
+ continue;
+ }
+ else if ( result <= 0 ) {
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";
+ perror("System reports");
+ continue;
+ }
+
+ // This is a bit weird, but we now have to decode an ALSA MIDI
+ // event (back) into MIDI bytes. We'll ignore non-MIDI types.
+ if ( !continueSysex ) message.bytes.clear();
+
+ doDecode = false;
+ switch ( ev->type ) {
+
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+#if defined(__RTMIDI_DEBUG__)
+ std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";
+#endif
+ break;
+
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+#if defined(__RTMIDI_DEBUG__)
+ std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";
+ std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"
+ << (int) ev->data.connect.sender.port
+ << ", dest = " << (int) ev->data.connect.dest.client << ":"
+ << (int) ev->data.connect.dest.port
+ << std::endl;
+#endif
+ break;
+
+ case SND_SEQ_EVENT_QFRAME: // MIDI time code
+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+ break;
+
+ case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+ break;
+
+ case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+ break;
+
+ case SND_SEQ_EVENT_SENSING: // Active sensing
+ if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;
+ break;
+
+ case SND_SEQ_EVENT_SYSEX:
+ if ( (data->ignoreFlags & 0x01) ) break;
+ if ( ev->data.ext.len > apiData->bufferSize ) {
+ apiData->bufferSize = ev->data.ext.len;
+ free( buffer );
+ buffer = (unsigned char *) malloc( apiData->bufferSize );
+ if ( buffer == NULL ) {
+ data->doInput = false;
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";
+ break;
+ }
+ }
+ doDecode = true;
+ break;
+
+ default:
+ doDecode = true;
+ }
+
+ if ( doDecode ) {
+
+ nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
+ if ( nBytes > 0 ) {
+ // The ALSA sequencer has a maximum buffer size for MIDI sysex
+ // events of 256 bytes. If a device sends sysex messages larger
+ // than this, they are segmented into 256 byte chunks. So,
+ // we'll watch for this and concatenate sysex chunks into a
+ // single sysex message if necessary.
+ if ( !continueSysex )
+ message.bytes.assign( buffer, &buffer[nBytes] );
+ else
+ message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
+
+ continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
+ if ( !continueSysex ) {
+
+ // Calculate the time stamp:
+ message.timeStamp = 0.0;
+
+ // Method 1: Use the system time.
+ //(void)gettimeofday(&tv, (struct timezone *)NULL);
+ //time = (tv.tv_sec * 1000000) + tv.tv_usec;
+
+ // Method 2: Use the ALSA sequencer event time data.
+ // (thanks to Pedro Lopez-Cabanillas!).
+
+ // Using method from:
+ // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html
+
+ // Perform the carry for the later subtraction by updating y.
+ // Temp var y is timespec because computation requires signed types,
+ // while snd_seq_real_time_t has unsigned types.
+ snd_seq_real_time_t &x( ev->time.time );
+ struct timespec y;
+ y.tv_nsec = apiData->lastTime.tv_nsec;
+ y.tv_sec = apiData->lastTime.tv_sec;
+ if ( x.tv_nsec < y.tv_nsec ) {
+ int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1;
+ y.tv_nsec -= 1000000000 * nsec;
+ y.tv_sec += nsec;
+ }
+ if ( x.tv_nsec - y.tv_nsec > 1000000000 ) {
+ int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000;
+ y.tv_nsec += 1000000000 * nsec;
+ y.tv_sec -= nsec;
+ }
+
+ // Compute the time difference.
+ time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9;
+
+ apiData->lastTime = ev->time.time;
+
+ if ( data->firstMessage == true )
+ data->firstMessage = false;
+ else
+ message.timeStamp = time;
+ }
+ else {
+#if defined(__RTMIDI_DEBUG__)
+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
+#endif
+ }
+ }
+ }
+
+ snd_seq_free_event( ev );
+ if ( message.bytes.size() == 0 || continueSysex ) continue;
+
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( message.timeStamp, &message.bytes, data->userData );
+ }
+ else {
+ // As long as we haven't reached our queue size limit, push the message.
+ if ( !data->queue.push( message ) )
+ std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";
+ }
+ }
+
+ if ( buffer ) free( buffer );
+ snd_midi_event_free( apiData->coder );
+ apiData->coder = 0;
+ apiData->thread = apiData->dummy_thread_id;
+ return 0;
+}
+
+MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ MidiInAlsa::initialize( clientName );
+}
+
+MidiInAlsa :: ~MidiInAlsa()
+{
+ // Close a connection if it exists.
+ MidiInAlsa::closePort();
+
+ // Shutdown the input thread.
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( inputData_.doInput ) {
+ inputData_.doInput = false;
+ int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) );
+ (void) res;
+ if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+ pthread_join( data->thread, NULL );
+ }
+
+ // Cleanup.
+ close ( data->trigger_fds[0] );
+ close ( data->trigger_fds[1] );
+ if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_free_queue( data->seq, data->queue_id );
+#endif
+ snd_seq_close( data->seq );
+ delete data;
+}
+
+void MidiInAlsa :: initialize( const std::string& clientName )
+{
+ // Set up the ALSA sequencer client.
+ snd_seq_t *seq;
+ int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK );
+ if ( result < 0 ) {
+ errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Set client name.
+ snd_seq_set_client_name( seq, clientName.c_str() );
+
+ // Save our api-specific connection information.
+ AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
+ data->seq = seq;
+ data->portNum = -1;
+ data->vport = -1;
+ data->subscription = 0;
+ data->dummy_thread_id = pthread_self();
+ data->thread = data->dummy_thread_id;
+ data->trigger_fds[0] = -1;
+ data->trigger_fds[1] = -1;
+ data->bufferSize = inputData_.bufferSize;
+ apiData_ = (void *) data;
+ inputData_.apiData = (void *) data;
+
+ if ( pipe(data->trigger_fds) == -1 ) {
+ errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Create the input queue
+#ifndef AVOID_TIMESTAMPING
+ data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" );
+ // Set arbitrary tempo (mm=100) and resolution (240)
+ snd_seq_queue_tempo_t *qtempo;
+ snd_seq_queue_tempo_alloca( &qtempo );
+ snd_seq_queue_tempo_set_tempo( qtempo, 600000 );
+ snd_seq_queue_tempo_set_ppq( qtempo, 240 );
+ snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo );
+ snd_seq_drain_output( data->seq );
+#endif
+}
+
+// This function is used to count or get the pinfo structure for a given port number.
+unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
+{
+ snd_seq_client_info_t *cinfo;
+ int client;
+ int count = 0;
+ snd_seq_client_info_alloca( &cinfo );
+
+ snd_seq_client_info_set_client( cinfo, -1 );
+ while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
+ client = snd_seq_client_info_get_client( cinfo );
+ if ( client == 0 ) continue;
+ // Reset query info
+ snd_seq_port_info_set_client( pinfo, client );
+ snd_seq_port_info_set_port( pinfo, -1 );
+ while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
+ unsigned int atyp = snd_seq_port_info_get_type( pinfo );
+ if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&
+ ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) &&
+ ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue;
+
+ unsigned int caps = snd_seq_port_info_get_capability( pinfo );
+ if ( ( caps & type ) != type ) continue;
+ if ( count == portNumber ) return 1;
+ ++count;
+ }
+ }
+
+ // If a negative portNumber was used, return the port count.
+ if ( portNumber < 0 ) return count;
+ return 0;
+}
+
+unsigned int MidiInAlsa :: getPortCount()
+{
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+
+ AlsaMidiData *data = static_cast (apiData_);
+ return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
+}
+
+std::string MidiInAlsa :: getPortName( unsigned int portNumber )
+{
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ snd_seq_client_info_alloca( &cinfo );
+ snd_seq_port_info_alloca( &pinfo );
+
+ std::string stringName;
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
+ int cnum = snd_seq_port_info_get_client( pinfo );
+ snd_seq_get_any_client_info( data->seq, cnum, cinfo );
+ std::ostringstream os;
+ os << snd_seq_client_info_get_name( cinfo );
+ os << ":";
+ os << snd_seq_port_info_get_name( pinfo );
+ os << " "; // These lines added to make sure devices are listed
+ os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names
+ os << ":";
+ os << snd_seq_port_info_get_port( pinfo );
+ stringName = os.str();
+ return stringName;
+ }
+
+ // If we get here, we didn't find a match.
+ errorString_ = "MidiInAlsa::getPortName: error looking for port name!";
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+}
+
+void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ unsigned int nSrc = this->getPortCount();
+ if ( nSrc < 1 ) {
+ errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ snd_seq_port_info_t *src_pinfo;
+ snd_seq_port_info_alloca( &src_pinfo );
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
+ std::ostringstream ost;
+ ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ snd_seq_addr_t sender, receiver;
+ sender.client = snd_seq_port_info_get_client( src_pinfo );
+ sender.port = snd_seq_port_info_get_port( src_pinfo );
+ receiver.client = snd_seq_client_id( data->seq );
+
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+ if ( data->vport < 0 ) {
+ snd_seq_port_info_set_client( pinfo, 0 );
+ snd_seq_port_info_set_port( pinfo, 0 );
+ snd_seq_port_info_set_capability( pinfo,
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
+ snd_seq_port_info_set_type( pinfo,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION );
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_port_info_set_timestamping( pinfo, 1 );
+ snd_seq_port_info_set_timestamp_real( pinfo, 1 );
+ snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id );
+#endif
+ snd_seq_port_info_set_name( pinfo, portName.c_str() );
+ data->vport = snd_seq_create_port( data->seq, pinfo );
+
+ if ( data->vport < 0 ) {
+ errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ data->vport = snd_seq_port_info_get_port( pinfo );
+ }
+
+ receiver.port = data->vport;
+
+ if ( !data->subscription ) {
+ // Make subscription
+ if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) {
+ errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ snd_seq_port_subscribe_set_sender( data->subscription, &sender );
+ snd_seq_port_subscribe_set_dest( data->subscription, &receiver );
+ if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) {
+ snd_seq_port_subscribe_free( data->subscription );
+ data->subscription = 0;
+ errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ if ( inputData_.doInput == false ) {
+ // Start the input queue
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_start_queue( data->seq, data->queue_id, NULL );
+ snd_seq_drain_output( data->seq );
+#endif
+ // Start our MIDI input thread.
+ pthread_attr_t attr;
+ pthread_attr_init( &attr );
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+
+ inputData_.doInput = true;
+ int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ );
+ pthread_attr_destroy( &attr );
+ if ( err ) {
+ snd_seq_unsubscribe_port( data->seq, data->subscription );
+ snd_seq_port_subscribe_free( data->subscription );
+ data->subscription = 0;
+ inputData_.doInput = false;
+ errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+ error( RtMidiError::THREAD_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ connected_ = true;
+}
+
+void MidiInAlsa :: openVirtualPort( const std::string &portName )
+{
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( data->vport < 0 ) {
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+ snd_seq_port_info_set_capability( pinfo,
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
+ snd_seq_port_info_set_type( pinfo,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION );
+ snd_seq_port_info_set_midi_channels( pinfo, 16 );
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_port_info_set_timestamping( pinfo, 1 );
+ snd_seq_port_info_set_timestamp_real( pinfo, 1 );
+ snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id );
+#endif
+ snd_seq_port_info_set_name( pinfo, portName.c_str() );
+ data->vport = snd_seq_create_port( data->seq, pinfo );
+
+ if ( data->vport < 0 ) {
+ errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ data->vport = snd_seq_port_info_get_port( pinfo );
+ }
+
+ if ( inputData_.doInput == false ) {
+ // Wait for old thread to stop, if still running
+ if ( !pthread_equal( data->thread, data->dummy_thread_id ) )
+ pthread_join( data->thread, NULL );
+
+ // Start the input queue
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_start_queue( data->seq, data->queue_id, NULL );
+ snd_seq_drain_output( data->seq );
+#endif
+ // Start our MIDI input thread.
+ pthread_attr_t attr;
+ pthread_attr_init( &attr );
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+
+ inputData_.doInput = true;
+ int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ );
+ pthread_attr_destroy( &attr );
+ if ( err ) {
+ if ( data->subscription ) {
+ snd_seq_unsubscribe_port( data->seq, data->subscription );
+ snd_seq_port_subscribe_free( data->subscription );
+ data->subscription = 0;
+ }
+ inputData_.doInput = false;
+ errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+ error( RtMidiError::THREAD_ERROR, errorString_ );
+ return;
+ }
+ }
+}
+
+void MidiInAlsa :: closePort( void )
+{
+ AlsaMidiData *data = static_cast (apiData_);
+
+ if ( connected_ ) {
+ if ( data->subscription ) {
+ snd_seq_unsubscribe_port( data->seq, data->subscription );
+ snd_seq_port_subscribe_free( data->subscription );
+ data->subscription = 0;
+ }
+ // Stop the input queue
+#ifndef AVOID_TIMESTAMPING
+ snd_seq_stop_queue( data->seq, data->queue_id, NULL );
+ snd_seq_drain_output( data->seq );
+#endif
+ connected_ = false;
+ }
+
+ // Stop thread to avoid triggering the callback, while the port is intended to be closed
+ if ( inputData_.doInput ) {
+ inputData_.doInput = false;
+ int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) );
+ (void) res;
+ if ( !pthread_equal( data->thread, data->dummy_thread_id ) )
+ pthread_join( data->thread, NULL );
+ }
+}
+
+void MidiInAlsa :: setClientName( const std::string &clientName )
+{
+
+ AlsaMidiData *data = static_cast ( apiData_ );
+ snd_seq_set_client_name( data->seq, clientName.c_str() );
+
+}
+
+void MidiInAlsa :: setPortName( const std::string &portName )
+{
+ AlsaMidiData *data = static_cast (apiData_);
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+ snd_seq_get_port_info( data->seq, data->vport, pinfo );
+ snd_seq_port_info_set_name( pinfo, portName.c_str() );
+ snd_seq_set_port_info( data->seq, data->vport, pinfo );
+}
+
+//*********************************************************************//
+// API: LINUX ALSA
+// Class Definitions: MidiOutAlsa
+//*********************************************************************//
+
+MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi()
+{
+ MidiOutAlsa::initialize( clientName );
+}
+
+MidiOutAlsa :: ~MidiOutAlsa()
+{
+ // Close a connection if it exists.
+ MidiOutAlsa::closePort();
+
+ // Cleanup.
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
+ if ( data->coder ) snd_midi_event_free( data->coder );
+ if ( data->buffer ) free( data->buffer );
+ snd_seq_close( data->seq );
+ delete data;
+}
+
+void MidiOutAlsa :: initialize( const std::string& clientName )
+{
+ // Set up the ALSA sequencer client.
+ snd_seq_t *seq;
+ int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
+ if ( result1 < 0 ) {
+ errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Set client name.
+ snd_seq_set_client_name( seq, clientName.c_str() );
+
+ // Save our api-specific connection information.
+ AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
+ data->seq = seq;
+ data->portNum = -1;
+ data->vport = -1;
+ data->bufferSize = 32;
+ data->coder = 0;
+ data->buffer = 0;
+ int result = snd_midi_event_new( data->bufferSize, &data->coder );
+ if ( result < 0 ) {
+ delete data;
+ errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ data->buffer = (unsigned char *) malloc( data->bufferSize );
+ if ( data->buffer == NULL ) {
+ delete data;
+ errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+ error( RtMidiError::MEMORY_ERROR, errorString_ );
+ return;
+ }
+ snd_midi_event_init( data->coder );
+ apiData_ = (void *) data;
+}
+
+unsigned int MidiOutAlsa :: getPortCount()
+{
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+
+ AlsaMidiData *data = static_cast (apiData_);
+ return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
+}
+
+std::string MidiOutAlsa :: getPortName( unsigned int portNumber )
+{
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ snd_seq_client_info_alloca( &cinfo );
+ snd_seq_port_info_alloca( &pinfo );
+
+ std::string stringName;
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
+ int cnum = snd_seq_port_info_get_client( pinfo );
+ snd_seq_get_any_client_info( data->seq, cnum, cinfo );
+ std::ostringstream os;
+ os << snd_seq_client_info_get_name( cinfo );
+ os << ":";
+ os << snd_seq_port_info_get_name( pinfo );
+ os << " "; // These lines added to make sure devices are listed
+ os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names
+ os << ":";
+ os << snd_seq_port_info_get_port( pinfo );
+ stringName = os.str();
+ return stringName;
+ }
+
+ // If we get here, we didn't find a match.
+ errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+}
+
+void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ unsigned int nSrc = this->getPortCount();
+ if ( nSrc < 1 ) {
+ errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
+ std::ostringstream ost;
+ ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ snd_seq_addr_t sender, receiver;
+ receiver.client = snd_seq_port_info_get_client( pinfo );
+ receiver.port = snd_seq_port_info_get_port( pinfo );
+ sender.client = snd_seq_client_id( data->seq );
+
+ if ( data->vport < 0 ) {
+ data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
+ SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
+ if ( data->vport < 0 ) {
+ errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ sender.port = data->vport;
+
+ // Make subscription
+ if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) {
+ snd_seq_port_subscribe_free( data->subscription );
+ errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ snd_seq_port_subscribe_set_sender( data->subscription, &sender );
+ snd_seq_port_subscribe_set_dest( data->subscription, &receiver );
+ snd_seq_port_subscribe_set_time_update( data->subscription, 1 );
+ snd_seq_port_subscribe_set_time_real( data->subscription, 1 );
+ if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) {
+ snd_seq_port_subscribe_free( data->subscription );
+ errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ connected_ = true;
+}
+
+void MidiOutAlsa :: closePort( void )
+{
+ if ( connected_ ) {
+ AlsaMidiData *data = static_cast (apiData_);
+ snd_seq_unsubscribe_port( data->seq, data->subscription );
+ snd_seq_port_subscribe_free( data->subscription );
+ data->subscription = 0;
+ connected_ = false;
+ }
+}
+
+void MidiOutAlsa :: setClientName( const std::string &clientName )
+{
+
+ AlsaMidiData *data = static_cast ( apiData_ );
+ snd_seq_set_client_name( data->seq, clientName.c_str() );
+
+}
+
+void MidiOutAlsa :: setPortName( const std::string &portName )
+{
+ AlsaMidiData *data = static_cast (apiData_);
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca( &pinfo );
+ snd_seq_get_port_info( data->seq, data->vport, pinfo );
+ snd_seq_port_info_set_name( pinfo, portName.c_str() );
+ snd_seq_set_port_info( data->seq, data->vport, pinfo );
+}
+
+void MidiOutAlsa :: openVirtualPort( const std::string &portName )
+{
+ AlsaMidiData *data = static_cast (apiData_);
+ if ( data->vport < 0 ) {
+ data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
+ SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
+
+ if ( data->vport < 0 ) {
+ errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ }
+ }
+}
+
+void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size )
+{
+ long result;
+ AlsaMidiData *data = static_cast (apiData_);
+ unsigned int nBytes = static_cast (size);
+ if ( nBytes > data->bufferSize ) {
+ data->bufferSize = nBytes;
+ result = snd_midi_event_resize_buffer( data->coder, nBytes );
+ if ( result != 0 ) {
+ errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ free (data->buffer);
+ data->buffer = (unsigned char *) malloc( data->bufferSize );
+ if ( data->buffer == NULL ) {
+ errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+ error( RtMidiError::MEMORY_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ for ( unsigned int i=0; ibuffer[i] = message[i];
+
+ unsigned int offset = 0;
+ while (offset < nBytes) {
+ snd_seq_event_t ev;
+ snd_seq_ev_clear( &ev );
+ snd_seq_ev_set_source( &ev, data->vport );
+ snd_seq_ev_set_subs( &ev );
+ snd_seq_ev_set_direct( &ev );
+ result = snd_midi_event_encode( data->coder, data->buffer + offset,
+ (long)(nBytes - offset), &ev );
+ if ( result < 0 ) {
+ errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ if ( ev.type == SND_SEQ_EVENT_NONE ) {
+ errorString_ = "MidiOutAlsa::sendMessage: incomplete message!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ offset += result;
+
+ // Send the event.
+ result = snd_seq_event_output( data->seq, &ev );
+ if ( result < 0 ) {
+ errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+ }
+ snd_seq_drain_output( data->seq );
+}
+
+#endif // __LINUX_ALSA__
+
+
+//*********************************************************************//
+// API: Windows Multimedia Library (MM)
+//*********************************************************************//
+
+// API information deciphered from:
+// - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
+
+// Thanks to Jean-Baptiste Berruchon for the sysex code.
+
+#if defined(__WINDOWS_MM__)
+
+// The Windows MM API is based on the use of a callback function for
+// MIDI input. We convert the system specific time stamps to delta
+// time values.
+
+// Windows MM MIDI header files.
+#include
+#include
+
+// Convert a null-terminated wide string or ANSI-encoded string to UTF-8.
+static std::string ConvertToUTF8(const TCHAR *str)
+{
+ std::string u8str;
+ const WCHAR *wstr = L"";
+#if defined( UNICODE ) || defined( _UNICODE )
+ wstr = str;
+#else
+ // Convert from ANSI encoding to wide string
+ int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 );
+ std::wstring wstrtemp;
+ if ( wlength )
+ {
+ wstrtemp.assign( wlength - 1, 0 );
+ MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength );
+ wstr = &wstrtemp[0];
+ }
+#endif
+ // Convert from wide string to UTF-8
+ int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL );
+ if ( length )
+ {
+ u8str.assign( length - 1, 0 );
+ length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL );
+ }
+ return u8str;
+}
+
+// A structure to hold variables related to the CoreMIDI API
+// implementation.
+struct WinMidiData {
+ HMIDIIN inHandle; // Handle to Midi Input Device
+ HMIDIOUT outHandle; // Handle to Midi Output Device
+ DWORD lastTime;
+ MidiInApi::MidiMessage message;
+ std::vector sysexBuffer;
+ CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
+};
+
+//*********************************************************************//
+// API: Windows MM
+// Class Definitions: MidiInWinMM
+//*********************************************************************//
+
+static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,
+ UINT inputStatus,
+ DWORD_PTR instancePtr,
+ DWORD_PTR midiMessage,
+ DWORD timestamp )
+{
+ if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;
+
+ //MidiInApi::RtMidiInData *data = static_cast (instancePtr);
+ MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;
+ WinMidiData *apiData = static_cast (data->apiData);
+
+ // Calculate time stamp.
+ if ( data->firstMessage == true ) {
+ apiData->message.timeStamp = 0.0;
+ data->firstMessage = false;
+ }
+ else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
+
+ if ( inputStatus == MIM_DATA ) { // Channel or system message
+
+ // Make sure the first byte is a status byte.
+ unsigned char status = (unsigned char) (midiMessage & 0x000000FF);
+ if ( !(status & 0x80) ) return;
+
+ // Determine the number of bytes in the MIDI message.
+ unsigned short nBytes = 1;
+ if ( status < 0xC0 ) nBytes = 3;
+ else if ( status < 0xE0 ) nBytes = 2;
+ else if ( status < 0xF0 ) nBytes = 3;
+ else if ( status == 0xF1 ) {
+ if ( data->ignoreFlags & 0x02 ) return;
+ else nBytes = 2;
+ }
+ else if ( status == 0xF2 ) nBytes = 3;
+ else if ( status == 0xF3 ) nBytes = 2;
+ else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {
+ // A MIDI timing tick message and we're ignoring it.
+ return;
+ }
+ else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {
+ // A MIDI active sensing message and we're ignoring it.
+ return;
+ }
+
+ // Copy bytes to our MIDI message.
+ unsigned char *ptr = (unsigned char *) &midiMessage;
+ for ( int i=0; imessage.bytes.push_back( *ptr++ );
+ }
+ else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
+ MIDIHDR *sysex = ( MIDIHDR *) midiMessage;
+ if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {
+ // Sysex message and we're not ignoring it
+ for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )
+ apiData->message.bytes.push_back( sysex->lpData[i] );
+ }
+
+ // The WinMM API requires that the sysex buffer be requeued after
+ // input of each sysex message. Even if we are ignoring sysex
+ // messages, we still need to requeue the buffer in case the user
+ // decides to not ignore sysex messages in the future. However,
+ // it seems that WinMM calls this function with an empty sysex
+ // buffer when an application closes and in this case, we should
+ // avoid requeueing it, else the computer suddenly reboots after
+ // one or two minutes.
+ if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {
+ //if ( sysex->dwBytesRecorded > 0 ) {
+ EnterCriticalSection( &(apiData->_mutex) );
+ MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );
+ LeaveCriticalSection( &(apiData->_mutex) );
+ if ( result != MMSYSERR_NOERROR )
+ std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
+
+ if ( data->ignoreFlags & 0x01 ) return;
+ }
+ else return;
+ }
+
+ // Save the time of the last non-filtered message
+ apiData->lastTime = timestamp;
+
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );
+ }
+ else {
+ // As long as we haven't reached our queue size limit, push the message.
+ if ( !data->queue.push( apiData->message ) )
+ std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n";
+ }
+
+ // Clear the vector for the next input message.
+ apiData->message.bytes.clear();
+}
+
+MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ MidiInWinMM::initialize( clientName );
+}
+
+MidiInWinMM :: ~MidiInWinMM()
+{
+ // Close a connection if it exists.
+ MidiInWinMM::closePort();
+
+ WinMidiData *data = static_cast (apiData_);
+ DeleteCriticalSection( &(data->_mutex) );
+
+ // Cleanup.
+ delete data;
+}
+
+void MidiInWinMM :: initialize( const std::string& /*clientName*/ )
+{
+ // We'll issue a warning here if no devices are available but not
+ // throw an error since the user can plugin something later.
+ unsigned int nDevices = midiInGetNumDevs();
+ if ( nDevices == 0 ) {
+ errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";
+ error( RtMidiError::WARNING, errorString_ );
+ }
+
+ // Save our api-specific connection information.
+ WinMidiData *data = (WinMidiData *) new WinMidiData;
+ apiData_ = (void *) data;
+ inputData_.apiData = (void *) data;
+ data->message.bytes.clear(); // needs to be empty for first input message
+
+ if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) {
+ errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";
+ error( RtMidiError::WARNING, errorString_ );
+ }
+}
+
+void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ unsigned int nDevices = midiInGetNumDevs();
+ if (nDevices == 0) {
+ errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ if ( portNumber >= nDevices ) {
+ std::ostringstream ost;
+ ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ WinMidiData *data = static_cast (apiData_);
+ MMRESULT result = midiInOpen( &data->inHandle,
+ portNumber,
+ (DWORD_PTR)&midiInputCallback,
+ (DWORD_PTR)&inputData_,
+ CALLBACK_FUNCTION );
+ if ( result != MMSYSERR_NOERROR ) {
+ errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Allocate and init the sysex buffers.
+ data->sysexBuffer.resize( inputData_.bufferCount );
+ for ( unsigned int i=0; i < inputData_.bufferCount; ++i ) {
+ data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
+ data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ];
+ data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize;
+ data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
+ data->sysexBuffer[i]->dwFlags = 0;
+
+ result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+ if ( result != MMSYSERR_NOERROR ) {
+ midiInClose( data->inHandle );
+ data->inHandle = 0;
+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Register the buffer.
+ result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+ if ( result != MMSYSERR_NOERROR ) {
+ midiInClose( data->inHandle );
+ data->inHandle = 0;
+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ result = midiInStart( data->inHandle );
+ if ( result != MMSYSERR_NOERROR ) {
+ midiInClose( data->inHandle );
+ data->inHandle = 0;
+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ connected_ = true;
+}
+
+void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ )
+{
+ // This function cannot be implemented for the Windows MM MIDI API.
+ errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+void MidiInWinMM :: closePort( void )
+{
+ if ( connected_ ) {
+ WinMidiData *data = static_cast (apiData_);
+ EnterCriticalSection( &(data->_mutex) );
+ midiInReset( data->inHandle );
+ midiInStop( data->inHandle );
+
+ for ( size_t i=0; i < data->sysexBuffer.size(); ++i ) {
+ int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));
+ delete [] data->sysexBuffer[i]->lpData;
+ delete [] data->sysexBuffer[i];
+ if ( result != MMSYSERR_NOERROR ) {
+ midiInClose( data->inHandle );
+ data->inHandle = 0;
+ errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+ }
+
+ midiInClose( data->inHandle );
+ data->inHandle = 0;
+ connected_ = false;
+ LeaveCriticalSection( &(data->_mutex) );
+ }
+}
+
+void MidiInWinMM :: setClientName ( const std::string& )
+{
+
+ errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiInWinMM :: setPortName ( const std::string& )
+{
+
+ errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiInWinMM :: getPortCount()
+{
+ return midiInGetNumDevs();
+}
+
+std::string MidiInWinMM :: getPortName( unsigned int portNumber )
+{
+ std::string stringName;
+ unsigned int nDevices = midiInGetNumDevs();
+ if ( portNumber >= nDevices ) {
+ std::ostringstream ost;
+ ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+ }
+
+ MIDIINCAPS deviceCaps;
+ midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
+ stringName = ConvertToUTF8( deviceCaps.szPname );
+
+ // Next lines added to add the portNumber to the name so that
+ // the device's names are sure to be listed with individual names
+ // even when they have the same brand name
+#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
+ std::ostringstream os;
+ os << " ";
+ os << portNumber;
+ stringName += os.str();
+#endif
+
+ return stringName;
+}
+
+//*********************************************************************//
+// API: Windows MM
+// Class Definitions: MidiOutWinMM
+//*********************************************************************//
+
+MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi()
+{
+ MidiOutWinMM::initialize( clientName );
+}
+
+MidiOutWinMM :: ~MidiOutWinMM()
+{
+ // Close a connection if it exists.
+ MidiOutWinMM::closePort();
+
+ // Cleanup.
+ WinMidiData *data = static_cast (apiData_);
+ delete data;
+}
+
+void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )
+{
+ // We'll issue a warning here if no devices are available but not
+ // throw an error since the user can plug something in later.
+ unsigned int nDevices = midiOutGetNumDevs();
+ if ( nDevices == 0 ) {
+ errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";
+ error( RtMidiError::WARNING, errorString_ );
+ }
+
+ // Save our api-specific connection information.
+ WinMidiData *data = (WinMidiData *) new WinMidiData;
+ apiData_ = (void *) data;
+}
+
+unsigned int MidiOutWinMM :: getPortCount()
+{
+ return midiOutGetNumDevs();
+}
+
+std::string MidiOutWinMM :: getPortName( unsigned int portNumber )
+{
+ std::string stringName;
+ unsigned int nDevices = midiOutGetNumDevs();
+ if ( portNumber >= nDevices ) {
+ std::ostringstream ost;
+ ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::WARNING, errorString_ );
+ return stringName;
+ }
+
+ MIDIOUTCAPS deviceCaps;
+ midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) );
+ stringName = ConvertToUTF8( deviceCaps.szPname );
+
+ // Next lines added to add the portNumber to the name so that
+ // the device's names are sure to be listed with individual names
+ // even when they have the same brand name
+ std::ostringstream os;
+#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES
+ os << " ";
+ os << portNumber;
+ stringName += os.str();
+#endif
+
+ return stringName;
+}
+
+void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ )
+{
+ if ( connected_ ) {
+ errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ unsigned int nDevices = midiOutGetNumDevs();
+ if ( nDevices < 1 ) {
+ errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";
+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+ return;
+ }
+
+ if ( portNumber >= nDevices ) {
+ std::ostringstream ost;
+ ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+ return;
+ }
+
+ WinMidiData *data = static_cast (apiData_);
+ MMRESULT result = midiOutOpen( &data->outHandle,
+ portNumber,
+ (DWORD)NULL,
+ (DWORD)NULL,
+ CALLBACK_NULL );
+ if ( result != MMSYSERR_NOERROR ) {
+ errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ connected_ = true;
+}
+
+void MidiOutWinMM :: closePort( void )
+{
+ if ( connected_ ) {
+ WinMidiData *data = static_cast (apiData_);
+ // Disabled because midiOutReset triggers 0x7b (if any note was ON) and 0x79 "Reset All
+ // Controllers" (to all 16 channels) CC messages which is undesirable (see issue #222)
+ // midiOutReset( data->outHandle );
+
+ midiOutClose( data->outHandle );
+ data->outHandle = 0;
+ connected_ = false;
+ }
+}
+
+void MidiOutWinMM :: setClientName ( const std::string& )
+{
+
+ errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutWinMM :: setPortName ( const std::string& )
+{
+
+ errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ )
+{
+ // This function cannot be implemented for the Windows MM MIDI API.
+ errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size )
+{
+ if ( !connected_ ) return;
+
+ unsigned int nBytes = static_cast(size);
+ if ( nBytes == 0 ) {
+ errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ MMRESULT result;
+ WinMidiData *data = static_cast (apiData_);
+ if ( message[0] == 0xF0 ) { // Sysex message
+
+ // Allocate buffer for sysex data.
+ char *buffer = (char *) malloc( nBytes );
+ if ( buffer == NULL ) {
+ errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";
+ error( RtMidiError::MEMORY_ERROR, errorString_ );
+ return;
+ }
+
+ // Copy data to buffer.
+ for ( unsigned int i=0; ioutHandle, &sysex, sizeof( MIDIHDR ) );
+ if ( result != MMSYSERR_NOERROR ) {
+ free( buffer );
+ errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Send the message.
+ result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) );
+ if ( result != MMSYSERR_NOERROR ) {
+ free( buffer );
+ errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Unprepare the buffer and MIDIHDR.
+ while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 );
+ free( buffer );
+ }
+ else { // Channel or system message.
+
+ // Make sure the message size isn't too big.
+ if ( nBytes > 3 ) {
+ errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ // Pack MIDI bytes into double word.
+ DWORD packet;
+ unsigned char *ptr = (unsigned char *) &packet;
+ for ( unsigned int i=0; ioutHandle, packet );
+ if ( result != MMSYSERR_NOERROR ) {
+ errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ }
+ }
+}
+
+#endif // __WINDOWS_MM__
+
+
+//*********************************************************************//
+// API: UNIX JACK
+//
+// Written primarily by Alexander Svetalkin, with updates for delta
+// time by Gary Scavone, April 2011.
+//
+// *********************************************************************//
+
+#if defined(__UNIX_JACK__)
+
+// JACK header files
+#include
+#include
+#include
+#include
+#include
+#ifdef HAVE_SEMAPHORE
+ #include
+#endif
+
+#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer
+
+struct JackMidiData {
+ jack_client_t *client;
+ jack_port_t *port;
+ jack_ringbuffer_t *buff;
+ int buffMaxWrite; // actual writable size, usually 1 less than ringbuffer
+ jack_time_t lastTime;
+#ifdef HAVE_SEMAPHORE
+ sem_t sem_cleanup;
+ sem_t sem_needpost;
+#endif
+ MidiInApi :: RtMidiInData *rtMidiIn;
+ };
+
+//*********************************************************************//
+// API: JACK
+// Class Definitions: MidiInJack
+//*********************************************************************//
+
+static int jackProcessIn( jack_nframes_t nframes, void *arg )
+{
+ JackMidiData *jData = (JackMidiData *) arg;
+ MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;
+ jack_midi_event_t event;
+ jack_time_t time;
+
+ // Is port created?
+ if ( jData->port == NULL ) return 0;
+
+ void *buff = jack_port_get_buffer( jData->port, nframes );
+ bool& continueSysex = rtData->continueSysex;
+ unsigned char& ignoreFlags = rtData->ignoreFlags;
+
+ // We have midi events in buffer
+ int evCount = jack_midi_get_event_count( buff );
+ for (int j = 0; j < evCount; j++) {
+ MidiInApi::MidiMessage& message = rtData->message;
+ jack_midi_event_get( &event, buff, j );
+
+ // Compute the delta time.
+ time = jack_get_time();
+ if ( rtData->firstMessage == true ) {
+ message.timeStamp = 0.0;
+ rtData->firstMessage = false;
+ } else
+ message.timeStamp = ( time - jData->lastTime ) * 0.000001;
+
+ jData->lastTime = time;
+
+ if ( !continueSysex )
+ message.bytes.clear();
+
+ if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) {
+ // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx,
+ // copy the event buffer into the MIDI message struct.
+ for ( unsigned int i = 0; i < event.size; i++ )
+ message.bytes.push_back( event.buffer[i] );
+ }
+
+ switch ( event.buffer[0] ) {
+ case 0xF0:
+ // Start of a SysEx message
+ continueSysex = event.buffer[event.size - 1] != 0xF7;
+ if ( ignoreFlags & 0x01 ) continue;
+ break;
+ case 0xF1:
+ case 0xF8:
+ // MIDI Time Code or Timing Clock message
+ if ( ignoreFlags & 0x02 ) continue;
+ break;
+ case 0xFE:
+ // Active Sensing message
+ if ( ignoreFlags & 0x04 ) continue;
+ break;
+ default:
+ if ( continueSysex ) {
+ // Continuation of a SysEx message
+ continueSysex = event.buffer[event.size - 1] != 0xF7;
+ if ( ignoreFlags & 0x01 ) continue;
+ }
+ // All other MIDI messages
+ }
+
+ if ( !continueSysex ) {
+ // If not a continuation of a SysEx message,
+ // invoke the user callback function or queue the message.
+ if ( rtData->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;
+ callback( message.timeStamp, &message.bytes, rtData->userData );
+ }
+ else {
+ // As long as we haven't reached our queue size limit, push the message.
+ if ( !rtData->queue.push( message ) )
+ std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";
+ }
+ }
+ }
+
+ return 0;
+}
+
+MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ MidiInJack::initialize( clientName );
+}
+
+void MidiInJack :: initialize( const std::string& clientName )
+{
+ JackMidiData *data = new JackMidiData;
+ apiData_ = (void *) data;
+
+ data->rtMidiIn = &inputData_;
+ data->port = NULL;
+ data->client = NULL;
+ this->clientName = clientName;
+
+ connect();
+}
+
+void MidiInJack :: connect()
+{
+ JackMidiData *data = static_cast (apiData_);
+ if ( data->client )
+ return;
+
+ // Initialize JACK client
+ if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
+ errorString_ = "MidiInJack::initialize: JACK server not running?";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ jack_set_process_callback( data->client, jackProcessIn, data );
+ jack_activate( data->client );
+}
+
+MidiInJack :: ~MidiInJack()
+{
+ JackMidiData *data = static_cast (apiData_);
+ MidiInJack::closePort();
+
+ if ( data->client )
+ jack_client_close( data->client );
+ delete data;
+}
+
+void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+
+ connect();
+
+ // Creating new port
+ if ( data->port == NULL )
+ data->port = jack_port_register( data->client, portName.c_str(),
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+ if ( data->port == NULL ) {
+ errorString_ = "MidiInJack::openPort: JACK error creating port";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Connecting to the output
+ std::string name = getPortName( portNumber );
+ jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );
+
+ connected_ = true;
+}
+
+void MidiInJack :: openVirtualPort( const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+
+ connect();
+ if ( data->port == NULL )
+ data->port = jack_port_register( data->client, portName.c_str(),
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+ if ( data->port == NULL ) {
+ errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ }
+}
+
+unsigned int MidiInJack :: getPortCount()
+{
+ int count = 0;
+ JackMidiData *data = static_cast (apiData_);
+ connect();
+ if ( !data->client )
+ return 0;
+
+ // List of available ports
+ const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+ if ( ports == NULL ) return 0;
+ while ( ports[count] != NULL )
+ count++;
+
+ free( ports );
+
+ return count;
+}
+
+std::string MidiInJack :: getPortName( unsigned int portNumber )
+{
+ JackMidiData *data = static_cast (apiData_);
+ std::string retStr( "" );
+
+ connect();
+
+ // List of available ports
+ const char **ports = jack_get_ports( data->client, NULL,
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+ // Check port validity
+ if ( ports == NULL ) {
+ errorString_ = "MidiInJack::getPortName: no ports available!";
+ error( RtMidiError::WARNING, errorString_ );
+ return retStr;
+ }
+
+ unsigned int i;
+ for ( i=0; i (apiData_);
+
+ if ( data->port == NULL ) return;
+ jack_port_unregister( data->client, data->port );
+ data->port = NULL;
+
+ connected_ = false;
+}
+
+void MidiInJack:: setClientName( const std::string& )
+{
+
+ errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiInJack :: setPortName( const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+#ifdef JACK_HAS_PORT_RENAME
+ jack_port_rename( data->client, data->port, portName.c_str() );
+#else
+ jack_port_set_name( data->port, portName.c_str() );
+#endif
+}
+
+//*********************************************************************//
+// API: JACK
+// Class Definitions: MidiOutJack
+//*********************************************************************//
+
+// Jack process callback
+static int jackProcessOut( jack_nframes_t nframes, void *arg )
+{
+ JackMidiData *data = (JackMidiData *) arg;
+ jack_midi_data_t *midiData;
+ int space;
+
+ // Is port created?
+ if ( data->port == NULL ) return 0;
+
+ void *buff = jack_port_get_buffer( data->port, nframes );
+ jack_midi_clear_buffer( buff );
+
+ while ( jack_ringbuffer_peek( data->buff, (char *) &space, sizeof( space ) ) == sizeof(space) &&
+ jack_ringbuffer_read_space( data->buff ) >= sizeof(space) + space ) {
+ jack_ringbuffer_read_advance( data->buff, sizeof(space) );
+
+ midiData = jack_midi_event_reserve( buff, 0, space );
+ if ( midiData )
+ jack_ringbuffer_read( data->buff, (char *) midiData, (size_t) space );
+ else
+ jack_ringbuffer_read_advance( data->buff, (size_t) space );
+ }
+
+#ifdef HAVE_SEMAPHORE
+ if ( !sem_trywait( &data->sem_needpost ) )
+ sem_post( &data->sem_cleanup );
+#endif
+
+ return 0;
+}
+
+MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi()
+{
+ MidiOutJack::initialize( clientName );
+}
+
+void MidiOutJack :: initialize( const std::string& clientName )
+{
+ JackMidiData *data = new JackMidiData;
+ apiData_ = (void *) data;
+
+ data->port = NULL;
+ data->client = NULL;
+#ifdef HAVE_SEMAPHORE
+ sem_init( &data->sem_cleanup, 0, 0 );
+ sem_init( &data->sem_needpost, 0, 0 );
+#endif
+ this->clientName = clientName;
+
+ connect();
+}
+
+void MidiOutJack :: connect()
+{
+ JackMidiData *data = static_cast (apiData_);
+ if ( data->client )
+ return;
+
+ // Initialize output ringbuffers
+ data->buff = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+ data->buffMaxWrite = (int) jack_ringbuffer_write_space( data->buff );
+
+ // Initialize JACK client
+ if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) {
+ errorString_ = "MidiOutJack::initialize: JACK server not running?";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ jack_set_process_callback( data->client, jackProcessOut, data );
+ jack_activate( data->client );
+}
+
+MidiOutJack :: ~MidiOutJack()
+{
+ JackMidiData *data = static_cast (apiData_);
+ MidiOutJack::closePort();
+
+ // Cleanup
+ jack_ringbuffer_free( data->buff );
+ if ( data->client ) {
+ jack_client_close( data->client );
+ }
+
+#ifdef HAVE_SEMAPHORE
+ sem_destroy( &data->sem_cleanup );
+ sem_destroy( &data->sem_needpost );
+#endif
+
+ delete data;
+}
+
+void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+
+ connect();
+
+ // Creating new port
+ if ( data->port == NULL )
+ data->port = jack_port_register( data->client, portName.c_str(),
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+ if ( data->port == NULL ) {
+ errorString_ = "MidiOutJack::openPort: JACK error creating port";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ return;
+ }
+
+ // Connecting to the output
+ std::string name = getPortName( portNumber );
+ jack_connect( data->client, jack_port_name( data->port ), name.c_str() );
+
+ connected_ = true;
+}
+
+void MidiOutJack :: openVirtualPort( const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+
+ connect();
+ if ( data->port == NULL )
+ data->port = jack_port_register( data->client, portName.c_str(),
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+ if ( data->port == NULL ) {
+ errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ }
+}
+
+unsigned int MidiOutJack :: getPortCount()
+{
+ int count = 0;
+ JackMidiData *data = static_cast (apiData_);
+ connect();
+ if ( !data->client )
+ return 0;
+
+ // List of available ports
+ const char **ports = jack_get_ports( data->client, NULL,
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+ if ( ports == NULL ) return 0;
+ while ( ports[count] != NULL )
+ count++;
+
+ free( ports );
+
+ return count;
+}
+
+std::string MidiOutJack :: getPortName( unsigned int portNumber )
+{
+ JackMidiData *data = static_cast (apiData_);
+ std::string retStr("");
+
+ connect();
+
+ // List of available ports
+ const char **ports = jack_get_ports( data->client, NULL,
+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+ // Check port validity
+ if ( ports == NULL ) {
+ errorString_ = "MidiOutJack::getPortName: no ports available!";
+ error( RtMidiError::WARNING, errorString_ );
+ return retStr;
+ }
+
+ if ( ports[portNumber] == NULL ) {
+ std::ostringstream ost;
+ ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error( RtMidiError::WARNING, errorString_ );
+ }
+ else retStr.assign( ports[portNumber] );
+
+ free( ports );
+ return retStr;
+}
+
+void MidiOutJack :: closePort()
+{
+ JackMidiData *data = static_cast (apiData_);
+
+ if ( data->port == NULL ) return;
+
+#ifdef HAVE_SEMAPHORE
+ struct timespec ts;
+ if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) {
+ ts.tv_sec += 1; // wait max one second
+ sem_post( &data->sem_needpost );
+ sem_timedwait( &data->sem_cleanup, &ts );
+ }
+#endif
+
+ jack_port_unregister( data->client, data->port );
+ data->port = NULL;
+
+ connected_ = false;
+}
+
+void MidiOutJack:: setClientName( const std::string& )
+{
+
+ errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutJack :: setPortName( const std::string &portName )
+{
+ JackMidiData *data = static_cast (apiData_);
+#ifdef JACK_HAS_PORT_RENAME
+ jack_port_rename( data->client, data->port, portName.c_str() );
+#else
+ jack_port_set_name( data->port, portName.c_str() );
+#endif
+}
+
+void MidiOutJack :: sendMessage( const unsigned char *message, size_t size )
+{
+ int nBytes = static_cast(size);
+ JackMidiData *data = static_cast (apiData_);
+
+ if ( size + sizeof(nBytes) > (size_t) data->buffMaxWrite )
+ return;
+
+ while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size )
+ sched_yield();
+
+ // Write full message to buffer
+ jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) );
+ jack_ringbuffer_write( data->buff, ( const char * ) message, nBytes );
+}
+
+#endif // __UNIX_JACK__
+
+//*********************************************************************//
+// API: Web MIDI
+//
+// Written primarily by Atsushi Eno, February 2020.
+//
+// *********************************************************************//
+
+#if defined(__WEB_MIDI_API__)
+
+#include
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: WebMidiAccessShim
+//*********************************************************************//
+
+class WebMidiAccessShim
+{
+public:
+ WebMidiAccessShim();
+ ~WebMidiAccessShim();
+ std::string getPortName( unsigned int portNumber, bool isInput );
+};
+
+std::unique_ptr shim{nullptr};
+
+void ensureShim()
+{
+ if ( shim.get() != nullptr )
+ return;
+ shim.reset( new WebMidiAccessShim() );
+}
+
+bool checkWebMidiAvailability()
+{
+ ensureShim();
+
+ return MAIN_THREAD_EM_ASM_INT( {
+ if ( typeof window._rtmidi_internals_waiting === "undefined" ) {
+ console.log ( "Attempted to use Web MIDI API without trying to open it." );
+ return false;
+ }
+ if ( window._rtmidi_internals_waiting ) {
+ console.log ( "Attempted to use Web MIDI API while it is being queried." );
+ return false;
+ }
+ if ( _rtmidi_internals_midi_access == null ) {
+ console.log ( "Attempted to use Web MIDI API while it already turned out to be unavailable." );
+ return false;
+ }
+ return true;
+ } );
+}
+
+WebMidiAccessShim::WebMidiAccessShim()
+{
+ MAIN_THREAD_ASYNC_EM_ASM( {
+ if( typeof window._rtmidi_internals_midi_access !== "undefined" )
+ return;
+ if( typeof window._rtmidi_internals_waiting !== "undefined" ) {
+ console.log( "MIDI Access was requested while another request is in progress." );
+ return;
+ }
+
+ // define functions
+ window._rtmidi_internals_get_port_by_number = function( portNumber, isInput ) {
+ var midi = window._rtmidi_internals_midi_access;
+ var devices = isInput ? midi.inputs : midi.outputs;
+ var i = 0;
+ for (var device of devices.values()) {
+ if ( i == portNumber )
+ return device;
+ i++;
+ }
+ console.log( "MIDI " + (isInput ? "input" : "output") + " device of portNumber " + portNumber + " is not found.");
+ return null;
+ };
+
+ window._rtmidi_internals_waiting = true;
+ window.navigator.requestMIDIAccess( {"sysex": true} ).then( (midiAccess) => {
+ window._rtmidi_internals_midi_access = midiAccess;
+ window._rtmidi_internals_latest_message_timestamp = 0.0;
+ window._rtmidi_internals_waiting = false;
+ if( midiAccess == null ) {
+ console.log ( "Could not get access to MIDI API" );
+ }
+ } );
+ } );
+}
+
+WebMidiAccessShim::~WebMidiAccessShim()
+{
+}
+
+std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInput )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ char *ret = (char*) MAIN_THREAD_EM_ASM_INT( {
+ var port = window._rtmidi_internals_get_port_by_number($0, $1);
+ if( port == null)
+ return null;
+ var length = lengthBytesUTF8(port.name) + 1;
+ var ret = _malloc(length);
+ stringToUTF8(port.name, ret, length);
+ return ret;
+ }, portNumber, isInput);
+ if (ret == nullptr)
+ return "";
+ std::string s = ret;
+ free(ret);
+ return s;
+}
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: MidiInWeb
+//*********************************************************************//
+
+MidiInWeb::MidiInWeb( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ initialize( clientName );
+}
+
+MidiInWeb::~MidiInWeb( void )
+{
+ closePort();
+}
+
+extern "C" void EMSCRIPTEN_KEEPALIVE rtmidi_onMidiMessageProc( MidiInApi::RtMidiInData* data, uint8_t* inputBytes, int32_t length, double domHighResTimeStamp )
+{
+ auto &message = data->message;
+ message.bytes.resize(message.bytes.size() + length);
+ memcpy(message.bytes.data(), inputBytes, length);
+ // FIXME: handle timestamp
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( message.timeStamp, &message.bytes, data->userData );
+ }
+}
+
+void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName )
+{
+ if( !checkWebMidiAvailability() )
+ return;
+ if (open_port_number >= 0)
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ // In Web MIDI API world, there is no step to open a port, but we have to register the input callback instead.
+ var input = window._rtmidi_internals_get_port_by_number($0, true);
+ input.onmidimessage = function(e) {
+ // In RtMidi world, timestamps are delta time from previous message, while in Web MIDI world
+ // timestamps are relative to window creation time (i.e. kind of absolute time with window "epoch" time).
+ var rtmidiTimestamp = window._rtmidi_internals_latest_message_timestamp == 0.0 ? 0.0 : e.timeStamp - window._rtmidi_internals_latest_message_timestamp;
+ window._rtmidi_internals_latest_message_timestamp = e.timeStamp;
+ Module.ccall( 'rtmidi_onMidiMessageProc', 'void', ['number', 'array', 'number', 'number'], [$1, e.data, e.data.length, rtmidiTimestamp] );
+ };
+ }, portNumber, &inputData_ );
+ open_port_number = portNumber;
+}
+
+void MidiInWeb::openVirtualPort( const std::string &portName )
+{
+
+ errorString_ = "MidiInWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiInWeb::closePort( void )
+{
+ if( open_port_number < 0 )
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ var input = _rtmidi_internals_get_port_by_number($0, true);
+ if( input == null ) {
+ console.log( "Port #" + $0 + " could not be found.");
+ return;
+ }
+ // unregister event handler
+ input.onmidimessage = null;
+ }, open_port_number );
+ open_port_number = -1;
+}
+
+void MidiInWeb::setClientName( const std::string &clientName )
+{
+ client_name = clientName;
+}
+
+void MidiInWeb::setPortName( const std::string &portName )
+{
+
+ errorString_ = "MidiInWeb::setPortName: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiInWeb::getPortCount( void )
+{
+ if( !checkWebMidiAvailability() )
+ return 0;
+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.inputs.size; } );
+}
+
+std::string MidiInWeb::getPortName( unsigned int portNumber )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ return shim->getPortName( portNumber, true );
+}
+
+void MidiInWeb::initialize( const std::string& clientName )
+{
+ ensureShim();
+ setClientName( clientName );
+}
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: MidiOutWeb
+//*********************************************************************//
+
+MidiOutWeb::MidiOutWeb( const std::string &clientName )
+{
+ initialize( clientName );
+}
+
+MidiOutWeb::~MidiOutWeb( void )
+{
+ closePort();
+}
+
+void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName )
+{
+ if( !checkWebMidiAvailability() )
+ return;
+ if (open_port_number >= 0)
+ return;
+ // In Web MIDI API world, there is no step to open a port.
+
+ open_port_number = portNumber;
+}
+
+void MidiOutWeb::openVirtualPort( const std::string &portName )
+{
+
+ errorString_ = "MidiOutWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutWeb::closePort( void )
+{
+ // there is really nothing to do for output at JS side.
+ open_port_number = -1;
+}
+
+void MidiOutWeb::setClientName( const std::string &clientName )
+{
+ client_name = clientName;
+}
+
+void MidiOutWeb::setPortName( const std::string &portName )
+{
+
+ errorString_ = "MidiOutWeb::setPortName: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiOutWeb::getPortCount( void )
+{
+ if( !checkWebMidiAvailability() )
+ return 0;
+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.outputs.size; } );
+}
+
+std::string MidiOutWeb::getPortName( unsigned int portNumber )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ return shim->getPortName( portNumber, false );
+}
+
+void MidiOutWeb::sendMessage( const unsigned char *message, size_t size )
+{
+ if( open_port_number < 0 )
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ var output = _rtmidi_internals_get_port_by_number( $0, false );
+ if( output == null ) {
+ console.log( "Port #" + $0 + " could not be found.");
+ return;
+ }
+ var buf = new ArrayBuffer ($2);
+ var msg = new Uint8Array( buf );
+ msg.set( new Uint8Array( Module.HEAPU8.buffer.slice( $1, $1 + $2 ) ) );
+ output.send( msg );
+ }, open_port_number, message, size );
+}
+
+void MidiOutWeb::initialize( const std::string& clientName )
+{
+ if ( shim.get() != nullptr )
+ return;
+ shim.reset( new WebMidiAccessShim() );
+ setClientName( clientName );
+}
+
+#endif // __WEB_MIDI_API__
diff --git a/dpf/distrho/src/jackbridge/rtmidi/RtMidi.h b/dpf/distrho/src/jackbridge/rtmidi/RtMidi.h
new file mode 100644
index 0000000..a6f5b79
--- /dev/null
+++ b/dpf/distrho/src/jackbridge/rtmidi/RtMidi.h
@@ -0,0 +1,658 @@
+/**********************************************************************/
+/*! \class RtMidi
+ \brief An abstract base class for realtime MIDI input/output.
+
+ This class implements some common functionality for the realtime
+ MIDI input/output subclasses RtMidiIn and RtMidiOut.
+
+ RtMidi GitHub site: https://github.com/thestk/rtmidi
+ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/
+
+ RtMidi: realtime MIDI i/o C++ classes
+ Copyright (c) 2003-2021 Gary P. Scavone
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ Any person wishing to distribute modifications to the Software is
+ asked to send the modifications to the original developer so that
+ they can be incorporated into the canonical version. This is,
+ however, not a binding provision of this license.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**********************************************************************/
+
+/*!
+ \file RtMidi.h
+ */
+
+#ifndef RTMIDI_H
+#define RTMIDI_H
+
+#if defined _WIN32 || defined __CYGWIN__
+ #if defined(RTMIDI_EXPORT)
+ #define RTMIDI_DLL_PUBLIC __declspec(dllexport)
+ #else
+ #define RTMIDI_DLL_PUBLIC
+ #endif
+#else
+ #if __GNUC__ >= 4
+ #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) )
+ #else
+ #define RTMIDI_DLL_PUBLIC
+ #endif
+#endif
+
+#define RTMIDI_VERSION "5.0.0"
+
+#include
+#include
+#include
+#include
+
+
+/************************************************************************/
+/*! \class RtMidiError
+ \brief Exception handling class for RtMidi.
+
+ The RtMidiError class is quite simple but it does allow errors to be
+ "caught" by RtMidiError::Type. See the RtMidi documentation to know
+ which methods can throw an RtMidiError.
+*/
+/************************************************************************/
+
+class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception
+{
+ public:
+ //! Defined RtMidiError types.
+ enum Type {
+ WARNING, /*!< A non-critical error. */
+ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */
+ UNSPECIFIED, /*!< The default, unspecified error type. */
+ NO_DEVICES_FOUND, /*!< No devices found on system. */
+ INVALID_DEVICE, /*!< An invalid device ID was specified. */
+ MEMORY_ERROR, /*!< An error occured during memory allocation. */
+ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
+ INVALID_USE, /*!< The function was called incorrectly. */
+ DRIVER_ERROR, /*!< A system driver error occured. */
+ SYSTEM_ERROR, /*!< A system error occured. */
+ THREAD_ERROR /*!< A thread error occured. */
+ };
+
+ //! The constructor.
+ RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw()
+ : message_(message), type_(type) {}
+
+ //! The destructor.
+ virtual ~RtMidiError( void ) throw() {}
+
+ //! Prints thrown error message to stderr.
+ virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; }
+
+ //! Returns the thrown error message type.
+ virtual const Type& getType( void ) const throw() { return type_; }
+
+ //! Returns the thrown error message string.
+ virtual const std::string& getMessage( void ) const throw() { return message_; }
+
+ //! Returns the thrown error message as a c-style string.
+ virtual const char* what( void ) const throw() { return message_.c_str(); }
+
+ protected:
+ std::string message_;
+ Type type_;
+};
+
+//! RtMidi error callback function prototype.
+/*!
+ \param type Type of error.
+ \param errorText Error description.
+
+ Note that class behaviour is undefined after a critical error (not
+ a warning) is reported.
+ */
+typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData );
+
+class MidiApi;
+
+class RTMIDI_DLL_PUBLIC RtMidi
+{
+ public:
+
+ RtMidi(RtMidi&& other) noexcept;
+ //! MIDI API specifier arguments.
+ enum Api {
+ UNSPECIFIED, /*!< Search for a working compiled API. */
+ MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */
+ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */
+ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */
+ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */
+ RTMIDI_DUMMY, /*!< A compilable but non-functional API. */
+ WEB_MIDI_API, /*!< W3C Web MIDI API. */
+ NUM_APIS /*!< Number of values in this enum. */
+ };
+
+ //! A static function to determine the current RtMidi version.
+ static std::string getVersion( void ) throw();
+
+ //! A static function to determine the available compiled MIDI APIs.
+ /*!
+ The values returned in the std::vector can be compared against
+ the enumerated list values. Note that there can be more than one
+ API compiled for certain operating systems.
+ */
+ static void getCompiledApi( std::vector &apis ) throw();
+
+ //! Return the name of a specified compiled MIDI API.
+ /*!
+ This obtains a short lower-case name used for identification purposes.
+ This value is guaranteed to remain identical across library versions.
+ If the API is unknown, this function will return the empty string.
+ */
+ static std::string getApiName( RtMidi::Api api );
+
+ //! Return the display name of a specified compiled MIDI API.
+ /*!
+ This obtains a long name used for display purposes.
+ If the API is unknown, this function will return the empty string.
+ */
+ static std::string getApiDisplayName( RtMidi::Api api );
+
+ //! Return the compiled MIDI API having the given name.
+ /*!
+ A case insensitive comparison will check the specified name
+ against the list of compiled APIs, and return the one which
+ matches. On failure, the function returns UNSPECIFIED.
+ */
+ static RtMidi::Api getCompiledApiByName( const std::string &name );
+
+ //! Pure virtual openPort() function.
+ virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0;
+
+ //! Pure virtual openVirtualPort() function.
+ virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0;
+
+ //! Pure virtual getPortCount() function.
+ virtual unsigned int getPortCount() = 0;
+
+ //! Pure virtual getPortName() function.
+ virtual std::string getPortName( unsigned int portNumber = 0 ) = 0;
+
+ //! Pure virtual closePort() function.
+ virtual void closePort( void ) = 0;
+
+ void setClientName( const std::string &clientName );
+ void setPortName( const std::string &portName );
+
+ //! Returns true if a port is open and false if not.
+ /*!
+ Note that this only applies to connections made with the openPort()
+ function, not to virtual ports.
+ */
+ virtual bool isPortOpen( void ) const = 0;
+
+ //! Set an error callback function to be invoked when an error has occured.
+ /*!
+ The callback function will be called whenever an error has occured. It is best
+ to set the error callback function before opening a port.
+ */
+ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0;
+
+ protected:
+ RtMidi();
+ virtual ~RtMidi();
+ MidiApi *rtapi_;
+
+ /* Make the class non-copyable */
+ RtMidi(RtMidi& other) = delete;
+ RtMidi& operator=(RtMidi& other) = delete;
+};
+
+/**********************************************************************/
+/*! \class RtMidiIn
+ \brief A realtime MIDI input class.
+
+ This class provides a common, platform-independent API for
+ realtime MIDI input. It allows access to a single MIDI input
+ port. Incoming MIDI messages are either saved to a queue for
+ retrieval using the getMessage() function or immediately passed to
+ a user-specified callback function. Create multiple instances of
+ this class to connect to more than one MIDI device at the same
+ time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also
+ possible to open a virtual input port to which other MIDI software
+ clients can connect.
+*/
+/**********************************************************************/
+
+// **************************************************************** //
+//
+// RtMidiIn and RtMidiOut class declarations.
+//
+// RtMidiIn / RtMidiOut are "controllers" used to select an available
+// MIDI input or output interface. They present common APIs for the
+// user to call but all functionality is implemented by the classes
+// MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut
+// each create an instance of a MidiInApi or MidiOutApi subclass based
+// on the user's API choice. If no choice is made, they attempt to
+// make a "logical" API selection.
+//
+// **************************************************************** //
+
+class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi
+{
+ public:
+ //! User callback function type definition.
+ typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData );
+
+ //! Default constructor that allows an optional api, client name and queue size.
+ /*!
+ An exception will be thrown if a MIDI system initialization
+ error occurs. The queue size defines the maximum number of
+ messages that can be held in the MIDI queue (when not using a
+ callback function). If the queue size limit is reached,
+ incoming messages will be ignored.
+
+ If no API argument is specified and multiple API support has been
+ compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+ JACK (OS-X).
+
+ \param api An optional API id can be specified.
+ \param clientName An optional client name can be specified. This
+ will be used to group the ports that are created
+ by the application.
+ \param queueSizeLimit An optional size of the MIDI input queue can be specified.
+ */
+ RtMidiIn( RtMidi::Api api=UNSPECIFIED,
+ const std::string& clientName = "RtMidi Input Client",
+ unsigned int queueSizeLimit = 100 );
+
+ RtMidiIn(RtMidiIn&& other) noexcept : RtMidi(std::move(other)) { }
+
+ //! If a MIDI connection is still open, it will be closed by the destructor.
+ ~RtMidiIn ( void ) throw();
+
+ //! Returns the MIDI API specifier for the current instance of RtMidiIn.
+ RtMidi::Api getCurrentApi( void ) throw();
+
+ //! Open a MIDI input connection given by enumeration number.
+ /*!
+ \param portNumber An optional port number greater than 0 can be specified.
+ Otherwise, the default or first port found is opened.
+ \param portName An optional name for the application port that is used to connect to portId can be specified.
+ */
+ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Input" ) );
+
+ //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only).
+ /*!
+ This function creates a virtual MIDI input port to which other
+ software applications can connect. This type of functionality
+ is currently only supported by the Macintosh OS-X, any JACK,
+ and Linux ALSA APIs (the function returns an error for the other APIs).
+
+ \param portName An optional name for the application port that is
+ used to connect to portId can be specified.
+ */
+ void openVirtualPort( const std::string &portName = std::string( "RtMidi Input" ) );
+
+ //! Set a callback function to be invoked for incoming MIDI messages.
+ /*!
+ The callback function will be called whenever an incoming MIDI
+ message is received. While not absolutely necessary, it is best
+ to set the callback function before opening a MIDI port to avoid
+ leaving some messages in the queue.
+
+ \param callback A callback function must be given.
+ \param userData Optionally, a pointer to additional data can be
+ passed to the callback function whenever it is called.
+ */
+ void setCallback( RtMidiCallback callback, void *userData = 0 );
+
+ //! Cancel use of the current callback function (if one exists).
+ /*!
+ Subsequent incoming MIDI messages will be written to the queue
+ and can be retrieved with the \e getMessage function.
+ */
+ void cancelCallback();
+
+ //! Close an open MIDI connection (if one exists).
+ void closePort( void );
+
+ //! Returns true if a port is open and false if not.
+ /*!
+ Note that this only applies to connections made with the openPort()
+ function, not to virtual ports.
+ */
+ virtual bool isPortOpen() const;
+
+ //! Return the number of available MIDI input ports.
+ /*!
+ \return This function returns the number of MIDI ports of the selected API.
+ */
+ unsigned int getPortCount();
+
+ //! Return a string identifier for the specified MIDI input port number.
+ /*!
+ \return The name of the port with the given Id is returned.
+ \retval An empty string is returned if an invalid port specifier
+ is provided. User code should assume a UTF-8 encoding.
+ */
+ std::string getPortName( unsigned int portNumber = 0 );
+
+ //! Specify whether certain MIDI message types should be queued or ignored during input.
+ /*!
+ By default, MIDI timing and active sensing messages are ignored
+ during message input because of their relative high data rates.
+ MIDI sysex messages are ignored by default as well. Variable
+ values of "true" imply that the respective message type will be
+ ignored.
+ */
+ void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true );
+
+ //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds.
+ /*!
+ This function returns immediately whether a new message is
+ available or not. A valid message is indicated by a non-zero
+ vector size. An exception is thrown if an error occurs during
+ message retrieval or an input connection was not previously
+ established.
+ */
+ double getMessage( std::vector *message );
+
+ //! Set an error callback function to be invoked when an error has occured.
+ /*!
+ The callback function will be called whenever an error has occured. It is best
+ to set the error callback function before opening a port.
+ */
+ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
+
+ //! Set maximum expected incoming message size.
+ /*!
+ For APIs that require manual buffer management, it can be useful to set the buffer
+ size and buffer count when expecting to receive large SysEx messages. Note that
+ currently this function has no effect when called after openPort(). The default
+ buffer size is 1024 with a count of 4 buffers, which should be sufficient for most
+ cases; as mentioned, this does not affect all API backends, since most either support
+ dynamically scalable buffers or take care of buffer handling themselves. It is
+ principally intended for users of the Windows MM backend who must support receiving
+ especially large messages.
+ */
+ virtual void setBufferSize( unsigned int size, unsigned int count );
+
+ protected:
+ void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit );
+};
+
+/**********************************************************************/
+/*! \class RtMidiOut
+ \brief A realtime MIDI output class.
+
+ This class provides a common, platform-independent API for MIDI
+ output. It allows one to probe available MIDI output ports, to
+ connect to one such port, and to send MIDI bytes immediately over
+ the connection. Create multiple instances of this class to
+ connect to more than one MIDI device at the same time. With the
+ OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a
+ virtual port to which other MIDI software clients can connect.
+*/
+/**********************************************************************/
+
+class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi
+{
+ public:
+ //! Default constructor that allows an optional client name.
+ /*!
+ An exception will be thrown if a MIDI system initialization error occurs.
+
+ If no API argument is specified and multiple API support has been
+ compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+ JACK (OS-X).
+ */
+ RtMidiOut( RtMidi::Api api=UNSPECIFIED,
+ const std::string& clientName = "RtMidi Output Client" );
+
+ RtMidiOut(RtMidiOut&& other) noexcept : RtMidi(std::move(other)) { }
+
+ //! The destructor closes any open MIDI connections.
+ ~RtMidiOut( void ) throw();
+
+ //! Returns the MIDI API specifier for the current instance of RtMidiOut.
+ RtMidi::Api getCurrentApi( void ) throw();
+
+ //! Open a MIDI output connection.
+ /*!
+ An optional port number greater than 0 can be specified.
+ Otherwise, the default or first port found is opened. An
+ exception is thrown if an error occurs while attempting to make
+ the port connection.
+ */
+ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Output" ) );
+
+ //! Close an open MIDI connection (if one exists).
+ void closePort( void );
+
+ //! Returns true if a port is open and false if not.
+ /*!
+ Note that this only applies to connections made with the openPort()
+ function, not to virtual ports.
+ */
+ virtual bool isPortOpen() const;
+
+ //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only).
+ /*!
+ This function creates a virtual MIDI output port to which other
+ software applications can connect. This type of functionality
+ is currently only supported by the Macintosh OS-X, Linux ALSA
+ and JACK APIs (the function does nothing with the other APIs).
+ An exception is thrown if an error occurs while attempting to
+ create the virtual port.
+ */
+ void openVirtualPort( const std::string &portName = std::string( "RtMidi Output" ) );
+
+ //! Return the number of available MIDI output ports.
+ unsigned int getPortCount( void );
+
+ //! Return a string identifier for the specified MIDI port type and number.
+ /*!
+ \return The name of the port with the given Id is returned.
+ \retval An empty string is returned if an invalid port specifier
+ is provided. User code should assume a UTF-8 encoding.
+ */
+ std::string getPortName( unsigned int portNumber = 0 );
+
+ //! Immediately send a single message out an open MIDI output port.
+ /*!
+ An exception is thrown if an error occurs during output or an
+ output connection was not previously established.
+ */
+ void sendMessage( const std::vector *message );
+
+ //! Immediately send a single message out an open MIDI output port.
+ /*!
+ An exception is thrown if an error occurs during output or an
+ output connection was not previously established.
+
+ \param message A pointer to the MIDI message as raw bytes
+ \param size Length of the MIDI message in bytes
+ */
+ void sendMessage( const unsigned char *message, size_t size );
+
+ //! Set an error callback function to be invoked when an error has occured.
+ /*!
+ The callback function will be called whenever an error has occured. It is best
+ to set the error callback function before opening a port.
+ */
+ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
+
+ protected:
+ void openMidiApi( RtMidi::Api api, const std::string &clientName );
+};
+
+
+// **************************************************************** //
+//
+// MidiInApi / MidiOutApi class declarations.
+//
+// Subclasses of MidiInApi and MidiOutApi contain all API- and
+// OS-specific code necessary to fully implement the RtMidi API.
+//
+// Note that MidiInApi and MidiOutApi are abstract base classes and
+// cannot be explicitly instantiated. RtMidiIn and RtMidiOut will
+// create instances of a MidiInApi or MidiOutApi subclass.
+//
+// **************************************************************** //
+
+class RTMIDI_DLL_PUBLIC MidiApi
+{
+ public:
+
+ MidiApi();
+ virtual ~MidiApi();
+ virtual RtMidi::Api getCurrentApi( void ) = 0;
+ virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0;
+ virtual void openVirtualPort( const std::string &portName ) = 0;
+ virtual void closePort( void ) = 0;
+ virtual void setClientName( const std::string &clientName ) = 0;
+ virtual void setPortName( const std::string &portName ) = 0;
+
+ virtual unsigned int getPortCount( void ) = 0;
+ virtual std::string getPortName( unsigned int portNumber ) = 0;
+
+ inline bool isPortOpen() const { return connected_; }
+ void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData );
+
+ //! A basic error reporting function for RtMidi classes.
+ void error( RtMidiError::Type type, std::string errorString );
+
+protected:
+ virtual void initialize( const std::string& clientName ) = 0;
+
+ void *apiData_;
+ bool connected_;
+ std::string errorString_;
+ RtMidiErrorCallback errorCallback_;
+ bool firstErrorOccurred_;
+ void *errorCallbackUserData_;
+
+};
+
+class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi
+{
+ public:
+
+ MidiInApi( unsigned int queueSizeLimit );
+ virtual ~MidiInApi( void );
+ void setCallback( RtMidiIn::RtMidiCallback callback, void *userData );
+ void cancelCallback( void );
+ virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense );
+ double getMessage( std::vector *message );
+ virtual void setBufferSize( unsigned int size, unsigned int count );
+
+ // A MIDI structure used internally by the class to store incoming
+ // messages. Each message represents one and only one MIDI message.
+ struct MidiMessage {
+ std::vector bytes;
+
+ //! Time in seconds elapsed since the previous message
+ double timeStamp;
+
+ // Default constructor.
+ MidiMessage()
+ : bytes(0), timeStamp(0.0) {}
+ };
+
+ struct MidiQueue {
+ unsigned int front;
+ unsigned int back;
+ unsigned int ringSize;
+ MidiMessage *ring;
+
+ // Default constructor.
+ MidiQueue()
+ : front(0), back(0), ringSize(0), ring(0) {}
+ bool push( const MidiMessage& );
+ bool pop( std::vector*, double* );
+ unsigned int size( unsigned int *back=0, unsigned int *front=0 );
+ };
+
+ // The RtMidiInData structure is used to pass private class data to
+ // the MIDI input handling function or thread.
+ struct RtMidiInData {
+ MidiQueue queue;
+ MidiMessage message;
+ unsigned char ignoreFlags;
+ bool doInput;
+ bool firstMessage;
+ void *apiData;
+ bool usingCallback;
+ RtMidiIn::RtMidiCallback userCallback;
+ void *userData;
+ bool continueSysex;
+ unsigned int bufferSize;
+ unsigned int bufferCount;
+
+ // Default constructor.
+ RtMidiInData()
+ : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false),
+ userCallback(0), userData(0), continueSysex(false), bufferSize(1024), bufferCount(4) {}
+ };
+
+ protected:
+ RtMidiInData inputData_;
+};
+
+class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi
+{
+ public:
+
+ MidiOutApi( void );
+ virtual ~MidiOutApi( void );
+ virtual void sendMessage( const unsigned char *message, size_t size ) = 0;
+};
+
+// **************************************************************** //
+//
+// Inline RtMidiIn and RtMidiOut definitions.
+//
+// **************************************************************** //
+
+inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { static_cast(rtapi_)->setCallback( callback, userData ); }
+inline void RtMidiIn :: cancelCallback( void ) { static_cast(rtapi_)->cancelCallback(); }
+inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }
+inline double RtMidiIn :: getMessage( std::vector *message ) { return static_cast(rtapi_)->getMessage( message ); }
+inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+inline void RtMidiIn :: setBufferSize( unsigned int size, unsigned int count ) { static_cast(rtapi_)->setBufferSize(size, count); }
+
+inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiOut :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiOut :: sendMessage( const std::vector *message ) { static_cast(rtapi_)->sendMessage( &message->at(0), message->size() ); }
+inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast(rtapi_)->sendMessage( message, size ); }
+inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+
+#endif
diff --git a/dpf/distrho/src/lv2/lv2.h b/dpf/distrho/src/lv2/lv2.h
index eaca514..cbf4f02 100644
--- a/dpf/distrho/src/lv2/lv2.h
+++ b/dpf/distrho/src/lv2/lv2.h
@@ -355,7 +355,9 @@ typedef struct _LV2_Descriptor {
Put this (LV2_SYMBOL_EXPORT) before any functions that are to be loaded
by the host as a symbol from the dynamic library.
*/
-#ifdef _WIN32
+#if defined(__EMSCRIPTEN__)
+# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __attribute__((used))
+#elif defined(_WIN32)
# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __declspec(dllexport)
#else
# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __attribute__((visibility("default")))
diff --git a/dpf/utils/plugin.app/Contents/Info.plist b/dpf/utils/plugin.app/Contents/Info.plist
new file mode 100644
index 0000000..332ada6
--- /dev/null
+++ b/dpf/utils/plugin.app/Contents/Info.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ @INFO_PLIST_PROJECT_NAME@
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ studio.kx.distrho.@INFO_PLIST_PROJECT_NAME@
+ NSHighResolutionCapable
+
+ NSRequiresAquaSystemAppearance
+
+ NSMicrophoneUsageDescription
+ @INFO_PLIST_PROJECT_NAME@ requires microphone permissions for audio input.
+
+
diff --git a/dpf/utils/plugin.vst/Contents/Info.plist b/dpf/utils/plugin.vst/Contents/Info.plist
index f2f15ff..6bc73e2 100644
--- a/dpf/utils/plugin.vst/Contents/Info.plist
+++ b/dpf/utils/plugin.vst/Contents/Info.plist
@@ -1,5 +1,5 @@
-
+
CFBundleDevelopmentRegion