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