| @@ -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 | |||
| @@ -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)/" $< > $@ | |||
| @@ -7,7 +7,7 @@ DPF is designed to make development of new plugins an easy and enjoyable task.<b | |||
| It allows developers to create plugins with custom UIs using a simple C++ API.<br/> | |||
| The framework facilitates exporting various different plugin formats from the same code-base.<br/> | |||
| DPF can build for LADSPA, DSSI, LV2 and VST formats.<br/> | |||
| DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.<br/> | |||
| All current plugin format implementations are complete.<br/> | |||
| A JACK/Standalone mode is also available, allowing you to quickly test plugins.<br/> | |||
| @@ -16,8 +16,10 @@ | |||
| #include "ApplicationPrivateData.hpp" | |||
| #ifdef __EMSCRIPTEN__ | |||
| #if defined(__EMSCRIPTEN__) | |||
| # include <emscripten/emscripten.h> | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| # include <CoreFoundation/CoreFoundation.h> | |||
| #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<CFTimeInterval>(idleTimeInMs) / 1000; | |||
| while (! pData->isQuitting) | |||
| { | |||
| pData->idle(0); | |||
| if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, idleTimeInSecs, true) == kCFRunLoopRunFinished) | |||
| break; | |||
| } | |||
| #else | |||
| while (! pData->isQuitting) | |||
| pData->idle(idleTimeInMs); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2019-2021 Jean Pierre Cimalando <jp-dev@inbox.ru> | |||
| * | |||
| * 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<uint>& 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<int>(s.getWidth()) == cairo_image_surface_get_width(newsurface),); | |||
| DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(s.getHeight()) == cairo_image_surface_get_height(newsurface),); | |||
| cairo_surface_destroy(surface); | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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; | |||
| }; | |||
| @@ -2,12 +2,9 @@ | |||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| // 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 <stdio.h> | |||
| @@ -15,6 +12,9 @@ | |||
| #include <EGL/egl.h> | |||
| // 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; | |||
| } | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -19,6 +19,7 @@ | |||
| #endif | |||
| #include "../extra/String.hpp" | |||
| #include "../DistrhoStandaloneUtils.hpp" | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # include <windows.h> | |||
| @@ -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 | |||
| @@ -34,7 +34,12 @@ | |||
| #endif | |||
| #include <cerrno> | |||
| #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) | |||
| @@ -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() | |||
| @@ -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<RtAudio> handle; | |||
| bool captureEnabled = false; | |||
| #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| std::vector<RtMidiIn> midiIns; | |||
| #endif | |||
| #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| std::vector<RtMidiOut> 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> 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<midiInCount; ++i) | |||
| { | |||
| try { | |||
| RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer()); | |||
| midiIn.setCallback(RtMidiCallback, this); | |||
| midiIn.openPort(i); | |||
| midiIns.push_back(std::move(midiIn)); | |||
| } catch (const RtMidiError& err) { | |||
| d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); | |||
| } DISTRHO_SAFE_EXCEPTION("midiIn.openPort()"); | |||
| } | |||
| #endif | |||
| #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| for (uint i=0; i<midiOutCount; ++i) | |||
| { | |||
| try { | |||
| RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer()); | |||
| midiOut.openPort(i); | |||
| midiOuts.push_back(std::move(midiOut)); | |||
| } catch (const RtMidiError& err) { | |||
| d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); | |||
| } DISTRHO_SAFE_EXCEPTION("midiOut.openPort()"); | |||
| } | |||
| #endif | |||
| return true; | |||
| } | |||
| /* RtAudio in macOS uses a different than usual way to handle audio block size, | |||
| * where RTAUDIO_MINIMIZE_LATENCY makes CoreAudio use very low latencies (around 15 samples). | |||
| * As such, dynamic buffer sizes are meaningless there. | |||
| */ | |||
| #ifndef DISTRHO_OS_MAC | |||
| bool supportsBufferSizeChanges() const override | |||
| { | |||
| return true; | |||
| } | |||
| bool requestBufferSizeChange(const uint32_t newBufferSize) override | |||
| { | |||
| // stop audio first | |||
| deactivate(); | |||
| close(); | |||
| // try to open with new buffer size | |||
| nextBufferSize = newBufferSize; | |||
| const bool ok = _open(captureEnabled); | |||
| if (!ok) | |||
| { | |||
| // revert to old buffer size if new one failed | |||
| nextBufferSize = bufferSize; | |||
| _open(captureEnabled); | |||
| } | |||
| if (bufferSizeCallback != nullptr) | |||
| bufferSizeCallback(bufferSize, jackBufferSizeArg); | |||
| activate(); | |||
| return ok; | |||
| } | |||
| #endif | |||
| bool _open(const bool withInput) | |||
| { | |||
| ScopedPointer<RtAudio> 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<uchar>* message, void* userData) | |||
| { | |||
| const size_t len = message->size(); | |||
| DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= kMaxMIDIInputMessageSize,); | |||
| RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData); | |||
| // TODO timestamp handling | |||
| self->midiInBufferPending.writeByte(static_cast<uint8_t>(len)); | |||
| self->midiInBufferPending.writeCustomData(message->data(), len); | |||
| for (uint8_t i=0; i<len; ++i) | |||
| self->midiInBufferPending.writeByte(0); | |||
| self->midiInBufferPending.commitWrite(); | |||
| } | |||
| #endif | |||
| }; | |||
| #endif // RTAUDIO_API_TYPE | |||
| @@ -21,6 +21,14 @@ | |||
| #include <SDL.h> | |||
| #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; | |||
| } | |||
| @@ -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); | |||
| @@ -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. | |||
| @@ -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 <exception> | |||
| #include <iostream> | |||
| #include <string> | |||
| #include <vector> | |||
| /************************************************************************/ | |||
| /*! \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<RtMidi::Api> &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<unsigned char> *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<unsigned char> *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<unsigned char> *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<unsigned char> *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<unsigned char> 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<unsigned char>*, 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<MidiInApi *>(rtapi_)->setCallback( callback, userData ); } | |||
| inline void RtMidiIn :: cancelCallback( void ) { static_cast<MidiInApi *>(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<MidiInApi *>(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } | |||
| inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return static_cast<MidiInApi *>(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<MidiInApi *>(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<unsigned char> *message ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( &message->at(0), message->size() ); } | |||
| inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( message, size ); } | |||
| inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } | |||
| #endif | |||
| @@ -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"))) | |||
| @@ -0,0 +1,20 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
| <plist version="1.0"> | |||
| <dict> | |||
| <key>CFBundleDevelopmentRegion</key> | |||
| <string>English</string> | |||
| <key>CFBundleExecutable</key> | |||
| <string>@INFO_PLIST_PROJECT_NAME@</string> | |||
| <key>CFBundleIconFile</key> | |||
| <string></string> | |||
| <key>CFBundleIdentifier</key> | |||
| <string>studio.kx.distrho.@INFO_PLIST_PROJECT_NAME@</string> | |||
| <key>NSHighResolutionCapable</key> | |||
| <true/> | |||
| <key>NSRequiresAquaSystemAppearance</key> | |||
| <false/> | |||
| <key>NSMicrophoneUsageDescription</key> | |||
| <string>@INFO_PLIST_PROJECT_NAME@ requires microphone permissions for audio input.</string> | |||
| </dict> | |||
| </plist> | |||
| @@ -1,5 +1,5 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
| <plist version="1.0"> | |||
| <dict> | |||
| <key>CFBundleDevelopmentRegion</key> | |||