| @@ -196,7 +196,10 @@ else | |||||
| # Common linker flags | # Common linker flags | ||||
| LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1,--gc-sections | 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 | LINK_OPTS += -Wl,--as-needed | ||||
| ifneq ($(SKIP_STRIPPING),true) | ifneq ($(SKIP_STRIPPING),true) | ||||
| LINK_OPTS += -Wl,--strip-all | LINK_OPTS += -Wl,--strip-all | ||||
| @@ -257,7 +260,7 @@ LINK_FLAGS = $(LINK_OPTS) $(LDFLAGS) | |||||
| ifeq ($(WASM),true) | ifeq ($(WASM),true) | ||||
| # Special flag for emscripten | # Special flag for emscripten | ||||
| LINK_FLAGS += -sLLD_REPORT_UNDEFINED | |||||
| LINK_FLAGS += -sENVIRONMENT=web -sLLD_REPORT_UNDEFINED | |||||
| else ifneq ($(MACOS),true) | else ifneq ($(MACOS),true) | ||||
| # Not available on MacOS | # Not available on MacOS | ||||
| LINK_FLAGS += -Wl,--no-undefined | LINK_FLAGS += -Wl,--no-undefined | ||||
| @@ -267,6 +270,11 @@ ifeq ($(MACOS_OLD),true) | |||||
| BUILD_CXX_FLAGS = $(BASE_FLAGS) $(CXXFLAGS) -DHAVE_CPP11_SUPPORT=0 | BUILD_CXX_FLAGS = $(BASE_FLAGS) $(CXXFLAGS) -DHAVE_CPP11_SUPPORT=0 | ||||
| endif | endif | ||||
| ifeq ($(WASM_CLIPBOARD),true) | |||||
| BUILD_CXX_FLAGS += -DPUGL_WASM_ASYNC_CLIPBOARD | |||||
| LINK_FLAGS += -sASYNCIFY -sASYNCIFY_IMPORTS=puglGetAsyncClipboardData | |||||
| endif | |||||
| ifeq ($(WASM_EXCEPTIONS),true) | ifeq ($(WASM_EXCEPTIONS),true) | ||||
| BUILD_CXX_FLAGS += -fexceptions | BUILD_CXX_FLAGS += -fexceptions | ||||
| LINK_FLAGS += -fexceptions | LINK_FLAGS += -fexceptions | ||||
| @@ -346,7 +354,13 @@ endif | |||||
| endif | endif | ||||
| # backwards compat, always available/enabled | # 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 | HAVE_JACK = true | ||||
| endif | |||||
| endif | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Set Generic DGL stuff | # Set Generic DGL stuff | ||||
| @@ -410,7 +424,9 @@ else ifeq ($(MACOS),true) | |||||
| OPENGL_FLAGS = -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations | OPENGL_FLAGS = -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations | ||||
| OPENGL_LIBS = -framework OpenGL | OPENGL_LIBS = -framework OpenGL | ||||
| else ifeq ($(WASM),true) | 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) | ifneq ($(USE_GLES3),true) | ||||
| OPENGL_LIBS = -sLEGACY_GL_EMULATION -sGL_UNSAFE_OPTS=0 | OPENGL_LIBS = -sLEGACY_GL_EMULATION -sGL_UNSAFE_OPTS=0 | ||||
| endif | endif | ||||
| @@ -81,20 +81,23 @@ endif | |||||
| else ifneq ($(SKIP_RTAUDIO_FALLBACK),true) | else ifneq ($(SKIP_RTAUDIO_FALLBACK),true) | ||||
| ifeq ($(MACOS),true) | ifeq ($(MACOS),true) | ||||
| JACK_LIBS += -framework CoreAudio -framework CoreFoundation | |||||
| JACK_LIBS += -framework CoreAudio -framework CoreFoundation -framework CoreMIDI | |||||
| else ifeq ($(WINDOWS),true) | else ifeq ($(WINDOWS),true) | ||||
| JACK_LIBS += -lole32 -lwinmm | JACK_LIBS += -lole32 -lwinmm | ||||
| # DirectSound | # DirectSound | ||||
| JACK_LIBS += -ldsound | JACK_LIBS += -ldsound | ||||
| # WASAPI | # WASAPI | ||||
| # JACK_LIBS += -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid | # JACK_LIBS += -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid | ||||
| else ifeq ($(HAVE_PULSEAUDIO),true) | |||||
| else | |||||
| ifeq ($(HAVE_PULSEAUDIO),true) | |||||
| JACK_FLAGS += $(PULSEAUDIO_FLAGS) | JACK_FLAGS += $(PULSEAUDIO_FLAGS) | ||||
| JACK_LIBS += $(PULSEAUDIO_LIBS) | JACK_LIBS += $(PULSEAUDIO_LIBS) | ||||
| else ifeq ($(HAVE_ALSA),true) | |||||
| endif | |||||
| ifeq ($(HAVE_ALSA),true) | |||||
| JACK_FLAGS += $(ALSA_FLAGS) | JACK_FLAGS += $(ALSA_FLAGS) | ||||
| JACK_LIBS += $(ALSA_LIBS) | JACK_LIBS += $(ALSA_LIBS) | ||||
| endif | endif | ||||
| endif | |||||
| ifeq ($(HAVE_RTAUDIO),true) | ifeq ($(HAVE_RTAUDIO),true) | ||||
| ifneq ($(HAIKU),true) | ifneq ($(HAIKU),true) | ||||
| @@ -119,6 +122,93 @@ ifeq ($(MACOS),true) | |||||
| OBJS_UI += $(BUILD_DIR)/DistrhoUI_macOS_$(NAME).mm.o | OBJS_UI += $(BUILD_DIR)/DistrhoUI_macOS_$(NAME).mm.o | ||||
| endif | 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 | # Set VST2 filename, either single binary or inside a bundle | ||||
| @@ -152,7 +242,18 @@ endif | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Set plugin binary file targets | # 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) | jack = $(TARGET_DIR)/$(NAME)$(APP_EXT) | ||||
| endif | |||||
| ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) | ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) | ||||
| dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) | dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) | ||||
| dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_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 | vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Resources/empty.lproj | ||||
| endif | 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 | # 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 | SYMBOLS_SHARED = -Wl,--version-script=$(DPF_PATH)/utils/symbols/shared.version | ||||
| endif | 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 | # Runtime test build | ||||
| @@ -415,7 +429,7 @@ $(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(E | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # JACK | # JACK | ||||
| jack: $(jack) | |||||
| jack: $(jack) $(jackfiles) | |||||
| ifeq ($(HAVE_DGL),true) | ifeq ($(HAVE_DGL),true) | ||||
| $(jack): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.o $(DGL_LIB) | $(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 | # 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 $@) | -@mkdir -p $(shell dirname $@) | ||||
| $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@ | $(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/> | 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/> | 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/> | All current plugin format implementations are complete.<br/> | ||||
| A JACK/Standalone mode is also available, allowing you to quickly test plugins.<br/> | A JACK/Standalone mode is also available, allowing you to quickly test plugins.<br/> | ||||
| @@ -16,8 +16,10 @@ | |||||
| #include "ApplicationPrivateData.hpp" | #include "ApplicationPrivateData.hpp" | ||||
| #ifdef __EMSCRIPTEN__ | |||||
| #if defined(__EMSCRIPTEN__) | |||||
| # include <emscripten/emscripten.h> | # include <emscripten/emscripten.h> | ||||
| #elif defined(DISTRHO_OS_MAC) | |||||
| # include <CoreFoundation/CoreFoundation.h> | |||||
| #endif | #endif | ||||
| START_NAMESPACE_DGL | START_NAMESPACE_DGL | ||||
| @@ -48,8 +50,18 @@ void Application::exec(const uint idleTimeInMs) | |||||
| { | { | ||||
| DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,); | DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,); | ||||
| #ifdef __EMSCRIPTEN__ | |||||
| #if defined(__EMSCRIPTEN__) | |||||
| emscripten_set_main_loop_arg(app_idle, this, 0, true); | 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 | #else | ||||
| while (! pData->isQuitting) | while (! pData->isQuitting) | ||||
| pData->idle(idleTimeInMs); | pData->idle(idleTimeInMs); | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * DISTRHO Plugin Framework (DPF) | * 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> | * 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 | * 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); | 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(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); | cairo_surface_destroy(surface); | ||||
| @@ -31,7 +31,7 @@ | |||||
| START_NAMESPACE_DGL | 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_DBG(msg) std::fprintf(stderr, "%s", msg); | ||||
| # define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); | # define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); | ||||
| # define DGL_DBGF std::fflush(stderr); | # define DGL_DBGF std::fflush(stderr); | ||||
| @@ -614,7 +614,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh | |||||
| void Window::PrivateData::onPuglExpose() | void Window::PrivateData::onPuglExpose() | ||||
| { | { | ||||
| DGL_DBG("PUGL: onPuglExpose\n"); | |||||
| // DGL_DBG("PUGL: onPuglExpose\n"); | |||||
| puglOnDisplayPrepare(view); | puglOnDisplayPrepare(view); | ||||
| @@ -23,13 +23,13 @@ | |||||
| PuglWorldInternals* | PuglWorldInternals* | ||||
| puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) | puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) | ||||
| { | { | ||||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| PuglWorldInternals* impl = | PuglWorldInternals* impl = | ||||
| (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals)); | (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals)); | ||||
| impl->scaleFactor = emscripten_get_device_pixel_ratio(); | impl->scaleFactor = emscripten_get_device_pixel_ratio(); | ||||
| printf("DONE: %s %d | -> %f\n", __func__, __LINE__, impl->scaleFactor); | |||||
| return impl; | return impl; | ||||
| } | } | ||||
| @@ -46,6 +46,28 @@ puglInitViewInternals(PuglWorld* const world) | |||||
| printf("DONE: %s %d\n", __func__, __LINE__); | printf("DONE: %s %d\n", __func__, __LINE__); | ||||
| PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); | 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; | return impl; | ||||
| } | } | ||||
| @@ -162,7 +184,7 @@ puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEve | |||||
| PuglEvent event = {{PUGL_NOTHING, 0}}; | PuglEvent event = {{PUGL_NOTHING, 0}}; | ||||
| event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; | 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.x = xevent.xkey.x; | ||||
| // event.key.y = xevent.xkey.y; | // event.key.y = xevent.xkey.y; | ||||
| // event.key.xRoot = xevent.xkey.x_root; | // event.key.xRoot = xevent.xkey.x_root; | ||||
| @@ -221,7 +243,7 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv | |||||
| PuglEvent event = {{PUGL_NOTHING, 0}}; | PuglEvent event = {{PUGL_NOTHING, 0}}; | ||||
| const double time = mouseEvent->timestamp / 1000; | |||||
| const double time = mouseEvent->timestamp / 1e3; | |||||
| const PuglMods state = translateModifiers(mouseEvent->ctrlKey, | const PuglMods state = translateModifiers(mouseEvent->ctrlKey, | ||||
| mouseEvent->shiftKey, | mouseEvent->shiftKey, | ||||
| mouseEvent->altKey, | mouseEvent->altKey, | ||||
| @@ -252,30 +274,27 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv | |||||
| } | } | ||||
| break; | break; | ||||
| case EMSCRIPTEN_EVENT_MOUSEMOVE: | 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 | // adjust local values for delta | ||||
| const double movementX = mouseEvent->movementX * scaleFactor; | const double movementX = mouseEvent->movementX * scaleFactor; | ||||
| const double movementY = mouseEvent->movementY * 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 | // 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 { | } 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; | event.motion.state = state; | ||||
| break; | break; | ||||
| @@ -301,6 +320,119 @@ puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEv | |||||
| return EM_FALSE; | 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 | static EM_BOOL | ||||
| puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData) | 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; | PuglView* const view = (PuglView*)userData; | ||||
| printf("puglPointerLockChangeCallback %d\n", event->isActive); | |||||
| view->impl->pointerLocked = event->isActive; | view->impl->pointerLocked = event->isActive; | ||||
| return EM_TRUE; | return EM_TRUE; | ||||
| } | } | ||||
| @@ -341,7 +472,7 @@ puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEv | |||||
| const double scaleFactor = view->world->impl->scaleFactor; | const double scaleFactor = view->world->impl->scaleFactor; | ||||
| PuglEvent event = {{PUGL_SCROLL, 0}}; | 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.x = wheelEvent->mouse.targetX; | ||||
| event.scroll.y = wheelEvent->mouse.targetY; | event.scroll.y = wheelEvent->mouse.targetY; | ||||
| event.scroll.xRoot = wheelEvent->mouse.screenX; | event.scroll.xRoot = wheelEvent->mouse.screenX; | ||||
| @@ -362,10 +493,19 @@ static EM_BOOL | |||||
| puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData) | puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData) | ||||
| { | { | ||||
| PuglView* const view = (PuglView*)userData; | PuglView* const view = (PuglView*)userData; | ||||
| const char* const className = view->world->className; | |||||
| // FIXME | // 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) | if (!width || !height) | ||||
| return EM_FALSE; | return EM_FALSE; | ||||
| @@ -383,6 +523,17 @@ puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void | |||||
| return EM_TRUE; | 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 | PuglStatus | ||||
| puglRealize(PuglView* const view) | puglRealize(PuglView* const view) | ||||
| { | { | ||||
| @@ -433,10 +584,19 @@ puglRealize(PuglView* const view) | |||||
| event.configure.height = view->frame.height; | event.configure.height = view->frame.height; | ||||
| puglDispatchEvent(view, &event); | 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_canvas_element_size(className, view->frame.width, view->frame.height); | ||||
| // emscripten_set_keypress_callback(className, view, false, puglKeyCallback); | // emscripten_set_keypress_callback(className, view, false, puglKeyCallback); | ||||
| emscripten_set_keydown_callback(className, view, false, puglKeyCallback); | emscripten_set_keydown_callback(className, view, false, puglKeyCallback); | ||||
| emscripten_set_keyup_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_mousedown_callback(className, view, false, puglMouseCallback); | ||||
| emscripten_set_mouseup_callback(className, view, false, puglMouseCallback); | emscripten_set_mouseup_callback(className, view, false, puglMouseCallback); | ||||
| emscripten_set_mousemove_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_mouseleave_callback(className, view, false, puglMouseCallback); | ||||
| emscripten_set_focusin_callback(className, view, false, puglFocusCallback); | emscripten_set_focusin_callback(className, view, false, puglFocusCallback); | ||||
| emscripten_set_focusout_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_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); | 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__); | printf("TODO: %s %d\n", __func__, __LINE__); | ||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| @@ -456,15 +616,14 @@ puglRealize(PuglView* const view) | |||||
| PuglStatus | PuglStatus | ||||
| puglShow(PuglView* const view) | puglShow(PuglView* const view) | ||||
| { | { | ||||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| view->visible = true; | view->visible = true; | ||||
| view->impl->needsRepaint = true; | |||||
| return puglPostRedisplay(view); | return puglPostRedisplay(view); | ||||
| } | } | ||||
| PuglStatus | PuglStatus | ||||
| puglHide(PuglView* const view) | puglHide(PuglView* const view) | ||||
| { | { | ||||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| view->visible = false; | view->visible = false; | ||||
| return PUGL_FAILURE; | return PUGL_FAILURE; | ||||
| } | } | ||||
| @@ -477,6 +636,7 @@ puglFreeViewInternals(PuglView* const view) | |||||
| if (view->backend) { | if (view->backend) { | ||||
| view->backend->destroy(view); | view->backend->destroy(view); | ||||
| } | } | ||||
| free(view->impl->clipboardData); | |||||
| free(view->impl->timers); | free(view->impl->timers); | ||||
| free(view->impl); | free(view->impl); | ||||
| } | } | ||||
| @@ -495,36 +655,6 @@ puglGrabFocus(PuglView*) | |||||
| return PUGL_FAILURE; | 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 | double | ||||
| puglGetScaleFactor(const PuglView* const view) | puglGetScaleFactor(const PuglView* const view) | ||||
| { | { | ||||
| @@ -535,22 +665,21 @@ puglGetScaleFactor(const PuglView* const view) | |||||
| double | double | ||||
| puglGetTime(const PuglWorld*) | puglGetTime(const PuglWorld*) | ||||
| { | { | ||||
| // d_stdout("DONE %s %d", __func__, __LINE__); | |||||
| return emscripten_get_now() / 1000; | |||||
| return emscripten_get_now() / 1e3; | |||||
| } | } | ||||
| PuglStatus | PuglStatus | ||||
| puglUpdate(PuglWorld* const world, const double timeout) | puglUpdate(PuglWorld* const world, const double timeout) | ||||
| { | { | ||||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| for (size_t i = 0; i < world->numViews; ++i) { | for (size_t i = 0; i < world->numViews; ++i) { | ||||
| PuglView* const view = world->views[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) { | if (!view->impl->needsRepaint) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| @@ -563,13 +692,6 @@ puglUpdate(PuglWorld* const world, const double timeout) | |||||
| event.expose.width = view->frame.width; | event.expose.width = view->frame.width; | ||||
| event.expose.height = view->frame.height; | event.expose.height = view->frame.height; | ||||
| puglDispatchEvent(view, &event); | 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; | return PUGL_SUCCESS; | ||||
| @@ -578,7 +700,6 @@ puglUpdate(PuglWorld* const world, const double timeout) | |||||
| PuglStatus | PuglStatus | ||||
| puglPostRedisplay(PuglView* const view) | puglPostRedisplay(PuglView* const view) | ||||
| { | { | ||||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| view->impl->needsRepaint = true; | view->impl->needsRepaint = true; | ||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| } | } | ||||
| @@ -586,7 +707,6 @@ puglPostRedisplay(PuglView* const view) | |||||
| PuglStatus | PuglStatus | ||||
| puglPostRedisplayRect(PuglView* const view, const PuglRect rect) | puglPostRedisplayRect(PuglView* const view, const PuglRect rect) | ||||
| { | { | ||||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| view->impl->needsRepaint = true; | view->impl->needsRepaint = true; | ||||
| return PUGL_FAILURE; | return PUGL_FAILURE; | ||||
| } | } | ||||
| @@ -594,14 +714,12 @@ puglPostRedisplayRect(PuglView* const view, const PuglRect rect) | |||||
| PuglNativeView | PuglNativeView | ||||
| puglGetNativeView(PuglView* const view) | puglGetNativeView(PuglView* const view) | ||||
| { | { | ||||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| PuglStatus | PuglStatus | ||||
| puglSetWindowTitle(PuglView* const view, const char* const title) | puglSetWindowTitle(PuglView* const view, const char* const title) | ||||
| { | { | ||||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| puglSetString(&view->title, title); | puglSetString(&view->title, title); | ||||
| emscripten_set_window_title(title); | emscripten_set_window_title(title); | ||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| @@ -613,7 +731,6 @@ puglSetSizeHint(PuglView* const view, | |||||
| const PuglSpan width, | const PuglSpan width, | ||||
| const PuglSpan height) | const PuglSpan height) | ||||
| { | { | ||||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| view->sizeHints[hint].width = width; | view->sizeHints[hint].width = width; | ||||
| view->sizeHints[hint].height = height; | view->sizeHints[hint].height = height; | ||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| @@ -659,7 +776,7 @@ puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout) | |||||
| timer->view = view; | timer->view = view; | ||||
| timer->id = id; | timer->id = id; | ||||
| emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1000, timer); | |||||
| emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1e3, timer); | |||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| } | } | ||||
| @@ -685,13 +802,98 @@ puglStopTimer(PuglView* const view, const uintptr_t id) | |||||
| return PUGL_FAILURE; | 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* | const void* | ||||
| puglGetClipboard(PuglView* const view, | puglGetClipboard(PuglView* const view, | ||||
| const uint32_t typeIndex, | const uint32_t typeIndex, | ||||
| size_t* const len) | size_t* const len) | ||||
| { | { | ||||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||||
| return NULL; | |||||
| return view->impl->clipboardData; | |||||
| } | } | ||||
| PuglStatus | PuglStatus | ||||
| @@ -700,8 +902,48 @@ puglSetClipboard(PuglView* const view, | |||||
| const void* const data, | const void* const data, | ||||
| const size_t len) | 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 | PuglStatus | ||||
| @@ -10,6 +10,8 @@ | |||||
| #include "pugl/pugl.h" | #include "pugl/pugl.h" | ||||
| // #define PUGL_WASM_ASYNC_CLIPBOARD | |||||
| struct PuglTimer { | struct PuglTimer { | ||||
| PuglView* view; | PuglView* view; | ||||
| uintptr_t id; | uintptr_t id; | ||||
| @@ -19,14 +21,24 @@ struct PuglWorldInternalsImpl { | |||||
| double scaleFactor; | double scaleFactor; | ||||
| }; | }; | ||||
| struct LastMotionValues { | |||||
| double x, y, xRoot, yRoot; | |||||
| }; | |||||
| struct PuglInternalsImpl { | struct PuglInternalsImpl { | ||||
| PuglSurface* surface; | PuglSurface* surface; | ||||
| bool needsRepaint; | bool needsRepaint; | ||||
| bool pointerLocked; | bool pointerLocked; | ||||
| uint32_t numTimers; | 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; | struct PuglTimer* timers; | ||||
| }; | }; | ||||
| @@ -2,12 +2,9 @@ | |||||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | ||||
| // SPDX-License-Identifier: ISC | // SPDX-License-Identifier: ISC | ||||
| // #include "attributes.h" | |||||
| #include "stub.h" | #include "stub.h" | ||||
| // #include "types.h" | |||||
| #include "wasm.h" | #include "wasm.h" | ||||
| // #include "pugl/gl.h" | |||||
| #include "pugl/pugl.h" | #include "pugl/pugl.h" | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| @@ -15,6 +12,9 @@ | |||||
| #include <EGL/egl.h> | #include <EGL/egl.h> | ||||
| // for performance reasons we can keep a single EGL context always active | |||||
| #define PUGL_WASM_SINGLE_EGL_CONTEXT | |||||
| typedef struct { | typedef struct { | ||||
| EGLDisplay display; | EGLDisplay display; | ||||
| EGLConfig config; | EGLConfig config; | ||||
| @@ -42,21 +42,15 @@ static PuglStatus | |||||
| puglWasmGlConfigure(PuglView* view) | puglWasmGlConfigure(PuglView* view) | ||||
| { | { | ||||
| PuglInternals* const impl = view->impl; | 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); | const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | ||||
| if (display == EGL_NO_DISPLAY) { | if (display == EGL_NO_DISPLAY) { | ||||
| printf("eglGetDisplay Failed\n"); | |||||
| return PUGL_CREATE_CONTEXT_FAILED; | return PUGL_CREATE_CONTEXT_FAILED; | ||||
| } | } | ||||
| int major, minor; | int major, minor; | ||||
| if (eglInitialize(display, &major, &minor) != EGL_TRUE) { | if (eglInitialize(display, &major, &minor) != EGL_TRUE) { | ||||
| printf("eglInitialize Failed\n"); | |||||
| return PUGL_CREATE_CONTEXT_FAILED; | return PUGL_CREATE_CONTEXT_FAILED; | ||||
| } | } | ||||
| @@ -64,33 +58,31 @@ puglWasmGlConfigure(PuglView* view) | |||||
| int numConfigs; | int numConfigs; | ||||
| if (eglGetConfigs(display, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | if (eglGetConfigs(display, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | ||||
| printf("eglGetConfigs Failed\n"); | |||||
| eglTerminate(display); | eglTerminate(display); | ||||
| return PUGL_CREATE_CONTEXT_FAILED; | return PUGL_CREATE_CONTEXT_FAILED; | ||||
| } | } | ||||
| // clang-format off | // clang-format off | ||||
| const EGLint attrs[] = { | 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 | EGL_NONE | ||||
| }; | }; | ||||
| // clang-format on | // clang-format on | ||||
| if (eglChooseConfig(display, attrs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | if (eglChooseConfig(display, attrs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | ||||
| printf("eglChooseConfig Failed\n"); | |||||
| eglTerminate(display); | eglTerminate(display); | ||||
| return PUGL_CREATE_CONTEXT_FAILED; | return PUGL_CREATE_CONTEXT_FAILED; | ||||
| } | } | ||||
| @@ -119,11 +111,9 @@ puglWasmGlConfigure(PuglView* view) | |||||
| view->hints[PUGL_SAMPLES] = | view->hints[PUGL_SAMPLES] = | ||||
| puglWasmGlGetAttrib(display, config, EGL_SAMPLES); | puglWasmGlGetAttrib(display, config, EGL_SAMPLES); | ||||
| // always enabled for EGL | |||||
| // double-buffering is always enabled for EGL | |||||
| view->hints[PUGL_DOUBLE_BUFFER] = 1; | view->hints[PUGL_DOUBLE_BUFFER] = 1; | ||||
| printf("TODO: %s %d | ok\n", __func__, __LINE__); | |||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| } | } | ||||
| @@ -131,39 +121,38 @@ PUGL_WARN_UNUSED_RESULT | |||||
| static PuglStatus | static PuglStatus | ||||
| puglWasmGlEnter(PuglView* view, const PuglExposeEvent* PUGL_UNUSED(expose)) | puglWasmGlEnter(PuglView* view, const PuglExposeEvent* PUGL_UNUSED(expose)) | ||||
| { | { | ||||
| // printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | ||||
| if (!surface || !surface->context || !surface->surface) { | if (!surface || !surface->context || !surface->surface) { | ||||
| return PUGL_FAILURE; | 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; | return eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context) ? PUGL_SUCCESS : PUGL_FAILURE; | ||||
| #else | |||||
| return PUGL_SUCCESS; | |||||
| #endif | |||||
| } | } | ||||
| PUGL_WARN_UNUSED_RESULT | PUGL_WARN_UNUSED_RESULT | ||||
| static PuglStatus | static PuglStatus | ||||
| puglWasmGlLeave(PuglView* view, const PuglExposeEvent* expose) | puglWasmGlLeave(PuglView* view, const PuglExposeEvent* expose) | ||||
| { | { | ||||
| // printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | ||||
| if (expose) { // note: swap buffers always enabled for EGL | if (expose) { // note: swap buffers always enabled for EGL | ||||
| eglSwapBuffers(surface->display, surface->surface); | 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; | return eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ? PUGL_SUCCESS : PUGL_FAILURE; | ||||
| #else | |||||
| return PUGL_SUCCESS; | |||||
| #endif | |||||
| } | } | ||||
| static PuglStatus | static PuglStatus | ||||
| puglWasmGlCreate(PuglView* view) | puglWasmGlCreate(PuglView* view) | ||||
| { | { | ||||
| printf("TODO: %s %d | start\n", __func__, __LINE__); | |||||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | ||||
| const EGLDisplay display = surface->display; | const EGLDisplay display = surface->display; | ||||
| const EGLConfig config = surface->config; | const EGLConfig config = surface->config; | ||||
| @@ -194,31 +183,18 @@ puglWasmGlCreate(PuglView* view) | |||||
| surface->context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); | surface->context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); | ||||
| if (surface->context == EGL_NO_CONTEXT) { | if (surface->context == EGL_NO_CONTEXT) { | ||||
| printf("eglCreateContext Failed\n"); | |||||
| return PUGL_CREATE_CONTEXT_FAILED; | 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); | surface->surface = eglCreateWindowSurface(display, config, 0, NULL); | ||||
| if (surface->surface == EGL_NO_SURFACE) { | if (surface->surface == EGL_NO_SURFACE) { | ||||
| printf("eglCreateWindowSurface Failed\n"); | |||||
| return PUGL_CREATE_CONTEXT_FAILED; | 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); | eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context); | ||||
| #endif | |||||
| return PUGL_SUCCESS; | return PUGL_SUCCESS; | ||||
| } | } | ||||
| @@ -226,7 +202,6 @@ puglWasmGlCreate(PuglView* view) | |||||
| static void | static void | ||||
| puglWasmGlDestroy(PuglView* view) | puglWasmGlDestroy(PuglView* view) | ||||
| { | { | ||||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| PuglWasmGlSurface* surface = (PuglWasmGlSurface*)view->impl->surface; | PuglWasmGlSurface* surface = (PuglWasmGlSurface*)view->impl->surface; | ||||
| if (surface) { | if (surface) { | ||||
| const EGLDisplay display = surface->display; | const EGLDisplay display = surface->display; | ||||
| @@ -243,13 +218,11 @@ puglWasmGlDestroy(PuglView* view) | |||||
| const PuglBackend* | const PuglBackend* | ||||
| puglGlBackend(void) | puglGlBackend(void) | ||||
| { | { | ||||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||||
| static const PuglBackend backend = {puglWasmGlConfigure, | static const PuglBackend backend = {puglWasmGlConfigure, | ||||
| puglWasmGlCreate, | puglWasmGlCreate, | ||||
| puglWasmGlDestroy, | puglWasmGlDestroy, | ||||
| puglWasmGlEnter, | puglWasmGlEnter, | ||||
| puglWasmGlLeave, | puglWasmGlLeave, | ||||
| puglStubGetContext}; | puglStubGetContext}; | ||||
| return &backend; | return &backend; | ||||
| } | } | ||||
| @@ -25,11 +25,24 @@ START_NAMESPACE_DISTRHO | |||||
| * Standalone plugin related utilities */ | * 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. | Check if the current standalone supports audio input. | ||||
| */ | */ | ||||
| @@ -58,7 +71,7 @@ bool isMIDIEnabled(); | |||||
| /** | /** | ||||
| Get the current buffer size. | Get the current buffer size. | ||||
| */ | */ | ||||
| uint32_t getBufferSize(); | |||||
| uint getBufferSize(); | |||||
| /** | /** | ||||
| Request permissions to use audio input. | Request permissions to use audio input. | ||||
| @@ -69,7 +82,7 @@ bool requestAudioInput(); | |||||
| /** | /** | ||||
| Request change to a new buffer size. | Request change to a new buffer size. | ||||
| */ | */ | ||||
| bool requestBufferSizeChange(uint32_t newBufferSize); | |||||
| bool requestBufferSizeChange(uint newBufferSize); | |||||
| /** | /** | ||||
| Request permissions to use MIDI. | Request permissions to use MIDI. | ||||
| @@ -31,7 +31,7 @@ | |||||
| # include "../extra/Thread.hpp" | # include "../extra/Thread.hpp" | ||||
| #endif | #endif | ||||
| #if defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM) | |||||
| #if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM) | |||||
| # define JACKBRIDGE_DIRECT | # define JACKBRIDGE_DIRECT | ||||
| #endif | #endif | ||||
| @@ -19,6 +19,7 @@ | |||||
| #endif | #endif | ||||
| #include "../extra/String.hpp" | #include "../extra/String.hpp" | ||||
| #include "../DistrhoStandaloneUtils.hpp" | |||||
| #ifdef DISTRHO_OS_WINDOWS | #ifdef DISTRHO_OS_WINDOWS | ||||
| # include <windows.h> | # include <windows.h> | ||||
| @@ -141,6 +142,20 @@ const char* getResourcePath(const char* const bundlePath) noexcept | |||||
| return nullptr; | 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 | END_NAMESPACE_DISTRHO | ||||
| @@ -34,7 +34,12 @@ | |||||
| #endif | #endif | ||||
| #include <cerrno> | #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 | // in case JACK fails, we fallback to native bridges simulating JACK API | ||||
| #include "NativeBridge.hpp" | #include "NativeBridge.hpp" | ||||
| @@ -56,10 +61,18 @@ | |||||
| #endif | #endif | ||||
| #if defined(HAVE_RTAUDIO) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | #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" | # include "RtAudioBridge.hpp" | ||||
| # ifdef RTAUDIO_API_TYPE | # ifdef RTAUDIO_API_TYPE | ||||
| # include "rtaudio/RtAudio.cpp" | # include "rtaudio/RtAudio.cpp" | ||||
| # endif | # endif | ||||
| # ifdef RTMIDI_API_TYPE | |||||
| # include "rtmidi/RtMidi.cpp" | |||||
| # endif | |||||
| # undef Point | |||||
| # undef Size | |||||
| #endif | #endif | ||||
| #if defined(HAVE_SDL2) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | #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_remove_all_properties remove_all_properties_ptr; | ||||
| jacksym_set_property_change_callback set_property_change_callback_ptr; | jacksym_set_property_change_callback set_property_change_callback_ptr; | ||||
| #ifdef __WINE__ | |||||
| #ifdef __WINE__ | |||||
| jacksym_set_thread_creator set_thread_creator_ptr; | jacksym_set_thread_creator set_thread_creator_ptr; | ||||
| #endif | |||||
| #endif | |||||
| JackBridge() | JackBridge() | ||||
| : lib(nullptr), | : lib(nullptr), | ||||
| @@ -429,15 +442,11 @@ struct JackBridge { | |||||
| remove_properties_ptr(nullptr), | remove_properties_ptr(nullptr), | ||||
| remove_all_properties_ptr(nullptr), | remove_all_properties_ptr(nullptr), | ||||
| set_property_change_callback_ptr(nullptr) | set_property_change_callback_ptr(nullptr) | ||||
| #ifdef __WINE__ | |||||
| #ifdef __WINE__ | |||||
| , set_thread_creator_ptr(nullptr) | , 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) | #if defined(DISTRHO_OS_MAC) | ||||
| const char* const filename = "libjack.dylib"; | const char* const filename = "libjack.dylib"; | ||||
| #elif defined(DISTRHO_OS_WINDOWS) && defined(_WIN64) | #elif defined(DISTRHO_OS_WINDOWS) && defined(_WIN64) | ||||
| @@ -578,14 +587,16 @@ struct JackBridge { | |||||
| LIB_SYMBOL(remove_all_properties) | LIB_SYMBOL(remove_all_properties) | ||||
| LIB_SYMBOL(set_property_change_callback) | LIB_SYMBOL(set_property_change_callback) | ||||
| #ifdef __WINE__ | |||||
| #ifdef __WINE__ | |||||
| LIB_SYMBOL(set_thread_creator) | LIB_SYMBOL(set_thread_creator) | ||||
| #endif | |||||
| #endif | |||||
| #endif | |||||
| #undef JOIN | #undef JOIN | ||||
| #undef LIB_SYMBOL | #undef LIB_SYMBOL | ||||
| } | } | ||||
| #ifdef HAVE_JACK | |||||
| ~JackBridge() noexcept | ~JackBridge() noexcept | ||||
| { | { | ||||
| USE_NAMESPACE_DISTRHO | USE_NAMESPACE_DISTRHO | ||||
| @@ -596,6 +607,7 @@ struct JackBridge { | |||||
| lib = nullptr; | lib = nullptr; | ||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| DISTRHO_DECLARE_NON_COPYABLE(JackBridge); | DISTRHO_DECLARE_NON_COPYABLE(JackBridge); | ||||
| }; | }; | ||||
| @@ -2265,15 +2277,22 @@ bool jackbridge_set_property_change_callback(jack_client_t* client, JackProperty | |||||
| START_NAMESPACE_DISTRHO | START_NAMESPACE_DISTRHO | ||||
| bool supportsAudioInput() | |||||
| bool isUsingNativeAudio() noexcept | |||||
| { | { | ||||
| #if defined(JACKBRIDGE_DUMMY) | |||||
| #if defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT) | |||||
| return false; | return false; | ||||
| #elif !defined(JACKBRIDGE_DIRECT) | |||||
| #else | |||||
| return usingNativeBridge; | |||||
| #endif | |||||
| } | |||||
| bool supportsAudioInput() | |||||
| { | |||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | |||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| return nativeBridge->supportsAudioInput(); | return nativeBridge->supportsAudioInput(); | ||||
| #endif | #endif | ||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| bool supportsBufferSizeChanges() | bool supportsBufferSizeChanges() | ||||
| @@ -2287,38 +2306,32 @@ bool supportsBufferSizeChanges() | |||||
| bool supportsMIDI() | bool supportsMIDI() | ||||
| { | { | ||||
| #if defined(JACKBRIDGE_DUMMY) | |||||
| return false; | |||||
| #elif !defined(JACKBRIDGE_DIRECT) | |||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | |||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| return nativeBridge->supportsMIDI(); | return nativeBridge->supportsMIDI(); | ||||
| #endif | #endif | ||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| bool isAudioInputEnabled() | bool isAudioInputEnabled() | ||||
| { | { | ||||
| #if defined(JACKBRIDGE_DUMMY) | |||||
| return false; | |||||
| #elif !defined(JACKBRIDGE_DIRECT) | |||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | |||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| return nativeBridge->isAudioInputEnabled(); | return nativeBridge->isAudioInputEnabled(); | ||||
| #endif | #endif | ||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| bool isMIDIEnabled() | bool isMIDIEnabled() | ||||
| { | { | ||||
| #if defined(JACKBRIDGE_DUMMY) | |||||
| return false; | |||||
| #elif !defined(JACKBRIDGE_DIRECT) | |||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | |||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| return nativeBridge->isMIDIEnabled(); | return nativeBridge->isMIDIEnabled(); | ||||
| #endif | #endif | ||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| uint32_t getBufferSize() | |||||
| uint getBufferSize() | |||||
| { | { | ||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | ||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| @@ -2336,7 +2349,7 @@ bool requestAudioInput() | |||||
| return false; | return false; | ||||
| } | } | ||||
| bool requestBufferSizeChange(const uint32_t newBufferSize) | |||||
| bool requestBufferSizeChange(const uint newBufferSize) | |||||
| { | { | ||||
| #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | #if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) | ||||
| if (usingNativeBridge) | if (usingNativeBridge) | ||||
| @@ -205,10 +205,12 @@ struct NativeBridge { | |||||
| (void)time; | (void)time; | ||||
| } | } | ||||
| void allocBuffers() | |||||
| void allocBuffers(const bool audio, const bool midi) | |||||
| { | { | ||||
| DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,); | DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,); | ||||
| if (audio) | |||||
| { | |||||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | ||||
| audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)]; | audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)]; | ||||
| @@ -219,7 +221,10 @@ struct NativeBridge { | |||||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | ||||
| std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS); | std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS); | ||||
| #endif | #endif | ||||
| } | |||||
| if (midi) | |||||
| { | |||||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | ||||
| midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512); | midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512); | ||||
| midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512); | midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512); | ||||
| @@ -227,6 +232,7 @@ struct NativeBridge { | |||||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | ||||
| midiOutBuffer.createBuffer(2048); | midiOutBuffer.createBuffer(2048); | ||||
| #endif | #endif | ||||
| } | |||||
| } | } | ||||
| void freeBuffers() | void freeBuffers() | ||||
| @@ -26,28 +26,54 @@ | |||||
| #if defined(DISTRHO_OS_MAC) | #if defined(DISTRHO_OS_MAC) | ||||
| # define __MACOSX_CORE__ | # define __MACOSX_CORE__ | ||||
| # define RTAUDIO_API_TYPE MACOSX_CORE | # define RTAUDIO_API_TYPE MACOSX_CORE | ||||
| # define RTMIDI_API_TYPE MACOSX_CORE | |||||
| #elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER) | #elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER) | ||||
| # define __WINDOWS_DS__ | # define __WINDOWS_DS__ | ||||
| # define RTAUDIO_API_TYPE 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 | #endif | ||||
| #ifdef RTAUDIO_API_TYPE | #ifdef RTAUDIO_API_TYPE | ||||
| # define Point CorePoint /* fix conflict between DGL and macOS Point name */ | |||||
| # include "rtaudio/RtAudio.h" | # include "rtaudio/RtAudio.h" | ||||
| # undef Point | |||||
| # include "rtmidi/RtMidi.h" | |||||
| # include "../../extra/ScopedPointer.hpp" | # include "../../extra/ScopedPointer.hpp" | ||||
| # include "../../extra/String.hpp" | |||||
| using DISTRHO_NAMESPACE::ScopedPointer; | using DISTRHO_NAMESPACE::ScopedPointer; | ||||
| using DISTRHO_NAMESPACE::String; | |||||
| struct RtAudioBridge : NativeBridge { | struct RtAudioBridge : NativeBridge { | ||||
| // pointer to RtAudio instance | // pointer to RtAudio instance | ||||
| ScopedPointer<RtAudio> handle; | 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 | const char* getVersion() const noexcept | ||||
| { | { | ||||
| @@ -56,48 +82,8 @@ struct RtAudioBridge : NativeBridge { | |||||
| bool open(const char* const clientName) override | 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 | bool close() override | ||||
| @@ -111,6 +97,9 @@ struct RtAudioBridge : NativeBridge { | |||||
| } DISTRHO_SAFE_EXCEPTION("handle->abortStream()"); | } DISTRHO_SAFE_EXCEPTION("handle->abortStream()"); | ||||
| } | } | ||||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||||
| freeBuffers(); | |||||
| #endif | |||||
| handle = nullptr; | handle = nullptr; | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -137,6 +126,218 @@ struct RtAudioBridge : NativeBridge { | |||||
| return true; | 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, | static int RtAudioCallback(void* const outputBuffer, | ||||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | ||||
| void* const inputBuffer, | void* const inputBuffer, | ||||
| @@ -177,6 +378,23 @@ struct RtAudioBridge : NativeBridge { | |||||
| return 0; | 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 | #endif // RTAUDIO_API_TYPE | ||||
| @@ -21,6 +21,14 @@ | |||||
| #include <SDL.h> | #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 | #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | ||||
| # error SDL without audio does not make sense | # error SDL without audio does not make sense | ||||
| #endif | #endif | ||||
| @@ -131,7 +139,7 @@ struct SDL2Bridge : NativeBridge { | |||||
| sampleRate = receivedPlayback.freq; | sampleRate = receivedPlayback.freq; | ||||
| #endif | #endif | ||||
| allocBuffers(); | |||||
| allocBuffers(true, false); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -52,7 +52,7 @@ struct WebBridge : NativeBridge { | |||||
| return 0; | return 0; | ||||
| }) != 0; | }) != 0; | ||||
| #endif | #endif | ||||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | ||||
| midiAvailable = EM_ASM_INT({ | midiAvailable = EM_ASM_INT({ | ||||
| return typeof(navigator.requestMIDIAccess) === 'function' ? 1 : 0; | return typeof(navigator.requestMIDIAccess) === 'function' ? 1 : 0; | ||||
| @@ -111,13 +111,16 @@ struct WebBridge : NativeBridge { | |||||
| return false; | return false; | ||||
| } | } | ||||
| bufferSize = 2048; | |||||
| bufferSize = EM_ASM_INT({ | |||||
| var WAB = Module['WebAudioBridge']; | |||||
| return WAB['minimizeBufferSize'] ? 256 : 2048; | |||||
| }); | |||||
| sampleRate = EM_ASM_INT_V({ | sampleRate = EM_ASM_INT_V({ | ||||
| var WAB = Module['WebAudioBridge']; | var WAB = Module['WebAudioBridge']; | ||||
| return WAB.audioContext.sampleRate; | return WAB.audioContext.sampleRate; | ||||
| }); | }); | ||||
| allocBuffers(); | |||||
| allocBuffers(true, true); | |||||
| EM_ASM({ | EM_ASM({ | ||||
| var numInputs = $0; | var numInputs = $0; | ||||
| @@ -125,23 +128,28 @@ struct WebBridge : NativeBridge { | |||||
| var bufferSize = $2; | var bufferSize = $2; | ||||
| var WAB = Module['WebAudioBridge']; | var WAB = Module['WebAudioBridge']; | ||||
| var realBufferSize = WAB['minimizeBufferSize'] ? 2048 : bufferSize; | |||||
| var divider = realBufferSize / bufferSize; | |||||
| // main processor | // main processor | ||||
| WAB.processor = WAB.audioContext['createScriptProcessor'](bufferSize, numInputs, numOutputs); | |||||
| WAB.processor = WAB.audioContext['createScriptProcessor'](realBufferSize, numInputs, numOutputs); | |||||
| WAB.processor['onaudioprocess'] = function (e) { | WAB.processor['onaudioprocess'] = function (e) { | ||||
| // var timestamp = performance.now(); | // 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 | // resume/start playback on first click | ||||
| document.addEventListener('click', function(e) { | document.addEventListener('click', function(e) { | ||||
| var WAB = Module['WebAudioBridge']; | var WAB = Module['WebAudioBridge']; | ||||
| console.log(WAB.audioContext.state); | |||||
| if (WAB.audioContext.state === 'suspended') | if (WAB.audioContext.state === 'suspended') | ||||
| WAB.audioContext.resume(); | WAB.audioContext.resume(); | ||||
| }); | }); | ||||
| @@ -279,7 +286,7 @@ struct WebBridge : NativeBridge { | |||||
| bufferSize = newBufferSize; | bufferSize = newBufferSize; | ||||
| freeBuffers(); | freeBuffers(); | ||||
| allocBuffers(); | |||||
| allocBuffers(true, true); | |||||
| if (bufferSizeCallback != nullptr) | if (bufferSizeCallback != nullptr) | ||||
| bufferSizeCallback(newBufferSize, jackBufferSizeArg); | 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 | Put this (LV2_SYMBOL_EXPORT) before any functions that are to be loaded | ||||
| by the host as a symbol from the dynamic library. | 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) | # define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __declspec(dllexport) | ||||
| #else | #else | ||||
| # define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __attribute__((visibility("default"))) | # 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"?> | <?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"> | <plist version="1.0"> | ||||
| <dict> | <dict> | ||||
| <key>CFBundleDevelopmentRegion</key> | <key>CFBundleDevelopmentRegion</key> | ||||