| @@ -77,10 +77,12 @@ jackbridge-wine64: | |||||
| clean: | clean: | ||||
| rm -f *.a *.def *.dll *.dylib *.so | rm -f *.a *.def *.dll *.dylib *.so | ||||
| $(MAKE) clean -C carla_native | |||||
| $(MAKE) clean -C distrho/dgl | $(MAKE) clean -C distrho/dgl | ||||
| $(MAKE) clean -C lilv | |||||
| $(MAKE) clean -C jackbridge | $(MAKE) clean -C jackbridge | ||||
| $(MAKE) clean -C juce_audio_basics | |||||
| $(MAKE) clean -C juce_core | $(MAKE) clean -C juce_core | ||||
| $(MAKE) clean -C lilv | |||||
| $(MAKE) clean -C rtmempool | $(MAKE) clean -C rtmempool | ||||
| $(MAKE) clean -C theme | $(MAKE) clean -C theme | ||||
| $(MAKE) clean -C widgets | $(MAKE) clean -C widgets | ||||
| @@ -9,13 +9,12 @@ include ../../Makefile.mk | |||||
| # -------------------------------------------------------------- | # -------------------------------------------------------------- | ||||
| BUILD_C_FLAGS += -I. -I../../includes | BUILD_C_FLAGS += -I. -I../../includes | ||||
| BUILD_CXX_FLAGS += -I. -I.. -I../../includes -I../../utils | |||||
| BUILD_CXX_FLAGS += -I. -I.. -I../distrho -I../../includes -I../../utils | |||||
| # -------------------------------------------------------------- | # -------------------------------------------------------------- | ||||
| ifeq ($(HAVE_OPENGL),true) | ifeq ($(HAVE_OPENGL),true) | ||||
| GL_CXX_FLAGS = $(BUILD_CXX_FLAGS) | GL_CXX_FLAGS = $(BUILD_CXX_FLAGS) | ||||
| GL_CXX_FLAGS += -I../distrho | |||||
| GL_CXX_FLAGS += $(shell pkg-config --cflags gl) | GL_CXX_FLAGS += $(shell pkg-config --cflags gl) | ||||
| endif | endif | ||||
| @@ -83,9 +82,9 @@ OBJS += \ | |||||
| # distrho-stereoenhancer.cpp.o | # distrho-stereoenhancer.cpp.o | ||||
| endif | endif | ||||
| # # DISTRHO plugins (Qt) | |||||
| # OBJS += \ | |||||
| # distrho-notes.cpp.o | |||||
| # DISTRHO plugins (PyQt) | |||||
| OBJS += \ | |||||
| distrho-notes.cpp.o | |||||
| ifeq ($(HAVE_ZYN_DEPS),true) | ifeq ($(HAVE_ZYN_DEPS),true) | ||||
| # ZynAddSubFX | # ZynAddSubFX | ||||
| @@ -175,19 +174,19 @@ audio_decoder/%.c.o: audio_decoder/%.c | |||||
| audio-file.cpp.o: audio-file.cpp audio-base.hpp $(CXXDEPS) | audio-file.cpp.o: audio-file.cpp audio-base.hpp $(CXXDEPS) | ||||
| $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | ||||
| distrho-3bandeq.cpp.o: distrho-3bandeq.cpp 3bandeq/*.cpp 3bandeq/*.h 3bandeq/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| distrho-3bandeq.cpp.o: distrho-3bandeq.cpp #3bandeq/*.cpp 3bandeq/*.h 3bandeq/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| $(CXX) $< $(GL_CXX_FLAGS) -I3bandeq -DDISTRHO_NAMESPACE=DISTRHO_3BandEQ -c -o $@ | $(CXX) $< $(GL_CXX_FLAGS) -I3bandeq -DDISTRHO_NAMESPACE=DISTRHO_3BandEQ -c -o $@ | ||||
| distrho-3bandsplitter.cpp.o: distrho-3bandsplitter.cpp 3bandsplitter/*.cpp 3bandsplitter/*.h 3bandsplitter/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| distrho-3bandsplitter.cpp.o: distrho-3bandsplitter.cpp #3bandsplitter/*.cpp 3bandsplitter/*.h 3bandsplitter/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| $(CXX) $< $(GL_CXX_FLAGS) -I3bandsplitter -DDISTRHO_NAMESPACE=DISTRHO_3BandSplitter -c -o $@ | $(CXX) $< $(GL_CXX_FLAGS) -I3bandsplitter -DDISTRHO_NAMESPACE=DISTRHO_3BandSplitter -c -o $@ | ||||
| distrho-nekobi.cpp.o: distrho-nekobi.cpp nekobi/*.cpp nekobi/*.h nekobi/*.hpp nekobi/nekobee-src/*.c nekobi/nekobee-src/*.h distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| distrho-nekobi.cpp.o: distrho-nekobi.cpp nekobi/*.cpp #nekobi/*.h nekobi/*.hpp nekobi/nekobee-src/*.c nekobi/nekobee-src/*.h distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| $(CXX) $< $(GL_CXX_FLAGS) -Inekobi -DDISTRHO_NAMESPACE=DISTRHO_Nekobi -c -o $@ | $(CXX) $< $(GL_CXX_FLAGS) -Inekobi -DDISTRHO_NAMESPACE=DISTRHO_Nekobi -c -o $@ | ||||
| distrho-pingpongpan.cpp.o: distrho-pingpongpan.cpp pingpongpan/*.cpp pingpongpan/*.h pingpongpan/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| distrho-pingpongpan.cpp.o: distrho-pingpongpan.cpp #pingpongpan/*.cpp pingpongpan/*.h pingpongpan/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| $(CXX) $< $(GL_CXX_FLAGS) -Ipingpongpan -DDISTRHO_NAMESPACE=DISTRHO_PingPongPan -c -o $@ | $(CXX) $< $(GL_CXX_FLAGS) -Ipingpongpan -DDISTRHO_NAMESPACE=DISTRHO_PingPongPan -c -o $@ | ||||
| distrho-stereoenhancer.cpp.o: distrho-stereoenhancer.cpp stereoenhancer/*.cpp stereoenhancer/*.h stereoenhancer/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| distrho-stereoenhancer.cpp.o: distrho-stereoenhancer.cpp #stereoenhancer/*.cpp stereoenhancer/*.h stereoenhancer/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||||
| $(CXX) $< $(GL_CXX_FLAGS) -Istereoenhancer -DDISTRHO_NAMESPACE=DISTRHO_StereoEnhancer -c -o $@ | $(CXX) $< $(GL_CXX_FLAGS) -Istereoenhancer -DDISTRHO_NAMESPACE=DISTRHO_StereoEnhancer -c -o $@ | ||||
| distrho-notes.cpp.o: distrho-notes.cpp notes/*.cpp notes/*.h notes/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | distrho-notes.cpp.o: distrho-notes.cpp notes/*.cpp notes/*.h notes/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | ||||
| @@ -30,7 +30,7 @@ START_NAMESPACE_DISTRHO | |||||
| static const PluginDescriptor notesDesc = { | static const PluginDescriptor notesDesc = { | ||||
| /* category */ PLUGIN_CATEGORY_UTILITY, | /* category */ PLUGIN_CATEGORY_UTILITY, | ||||
| /* hints */ static_cast<PluginHints>(PLUGIN_IS_RTSAFE|PLUGIN_HAS_GUI|PLUGIN_USES_SINGLE_THREAD), | |||||
| /* hints */ static_cast<PluginHints>(PLUGIN_IS_RTSAFE|PLUGIN_HAS_GUI), | |||||
| /* supports */ static_cast<PluginSupports>(0x0), | /* supports */ static_cast<PluginSupports>(0x0), | ||||
| /* audioIns */ DISTRHO_PLUGIN_NUM_INPUTS, | /* audioIns */ DISTRHO_PLUGIN_NUM_INPUTS, | ||||
| /* audioOuts */ DISTRHO_PLUGIN_NUM_OUTPUTS, | /* audioOuts */ DISTRHO_PLUGIN_NUM_OUTPUTS, | ||||
| @@ -14,27 +14,39 @@ | |||||
| * For a full copy of the license see the LGPL.txt file | * For a full copy of the license see the LGPL.txt file | ||||
| */ | */ | ||||
| #include "../CarlaNative.hpp" | |||||
| #include "DistrhoPluginMain.cpp" | |||||
| #ifdef DISTRHO_UI_QT | #ifdef DISTRHO_UI_QT | ||||
| # error We do not want Qt in the engine code! | # error We do not want Qt in the engine code! | ||||
| #endif | #endif | ||||
| #ifdef DISTRHO_UI_EXTERNAL | |||||
| # error Not implemented yet | |||||
| #endif | |||||
| #include "../CarlaNative.hpp" | |||||
| #include "CarlaString.hpp" | |||||
| #include "DistrhoPluginMain.cpp" | |||||
| #define DISTRHO_PLUGIN_HAS_UI 1 | #define DISTRHO_PLUGIN_HAS_UI 1 | ||||
| #if DISTRHO_PLUGIN_HAS_UI | #if DISTRHO_PLUGIN_HAS_UI | ||||
| # include "DistrhoUIMain.cpp" | # include "DistrhoUIMain.cpp" | ||||
| # include "dgl/App.hpp" | |||||
| # include "dgl/Window.hpp" | |||||
| # ifdef DISTRHO_UI_OPENGL | |||||
| # include "dgl/App.hpp" | |||||
| # include "dgl/Window.hpp" | |||||
| # endif | |||||
| #endif | #endif | ||||
| using namespace DGL; | |||||
| #define WAIT_START_TIMEOUT 3000 /* ms */ | |||||
| #define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */ | |||||
| #define WAIT_STEP 100 /* ms */ | |||||
| #include <fcntl.h> | |||||
| //#include <sys/types.h> | |||||
| #include <sys/wait.h> | |||||
| using juce::ChildProcess; | |||||
| using juce::NamedPipe; | |||||
| using juce::Random; | |||||
| using juce::ScopedPointer; | using juce::ScopedPointer; | ||||
| using juce::String; | |||||
| using juce::StringArray; | |||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| @@ -51,82 +63,463 @@ public: | |||||
| : fHost(host), | : fHost(host), | ||||
| fPlugin(plugin), | fPlugin(plugin), | ||||
| fUi(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, uiResizeCallback), | fUi(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, uiResizeCallback), | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| glApp(fUi.getApp()), | glApp(fUi.getApp()), | ||||
| glWindow(fUi.getWindow()) | glWindow(fUi.getWindow()) | ||||
| #else | |||||
| pipeRecv(-1), | |||||
| pipeSend(-1), | |||||
| pid(-1) | |||||
| #endif | |||||
| { | { | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| glWindow.setSize(fUi.getWidth(), fUi.getHeight()); | glWindow.setSize(fUi.getWidth(), fUi.getHeight()); | ||||
| glWindow.setWindowTitle(host->uiName); | glWindow.setWindowTitle(host->uiName); | ||||
| #else | |||||
| const char* argv[6]; | |||||
| //------------------------------------------ | |||||
| // argv[0] => filename | |||||
| CarlaString filename; | |||||
| filename += fHost->resourceDir; | |||||
| #ifdef CARLA_OS_WIN | |||||
| filename += "\\resources\\"; | |||||
| #else | |||||
| filename += "/resources/"; | |||||
| #endif | |||||
| filename += fUi.getExternalFilename(); | |||||
| argv[0] = (const char*)filename; | |||||
| //------------------------------------------ | |||||
| // argv[1] => sample rate | |||||
| char sampleRateStr[12+1]; | |||||
| std::snprintf(sampleRateStr, 12, "%g", host->get_sample_rate(host->handle)); | |||||
| sampleRateStr[12] = '\0'; | |||||
| argv[1] = sampleRateStr; | |||||
| //------------------------------------------ | |||||
| // argv[2-3] => pipes | |||||
| int pipe1[2]; /* written by host process, read by plugin UI process */ | |||||
| int pipe2[2]; /* written by plugin UI process, read by host process */ | |||||
| if (pipe(pipe1) != 0) | |||||
| { | |||||
| fail("pipe1 creation failed"); | |||||
| return; | |||||
| } | |||||
| if (pipe(pipe2) != 0) | |||||
| { | |||||
| fail("pipe2 creation failed"); | |||||
| return; | |||||
| } | |||||
| char uiPipeRecv[100+1]; | |||||
| char uiPipeSend[100+1]; | |||||
| std::snprintf(uiPipeRecv, 100, "%d", pipe1[0]); /* [0] means reading end */ | |||||
| std::snprintf(uiPipeSend, 100, "%d", pipe2[1]); /* [1] means writting end */ | |||||
| uiPipeRecv[100] = '\0'; | |||||
| uiPipeSend[100] = '\0'; | |||||
| argv[2] = uiPipeRecv; /* reading end */ | |||||
| argv[3] = uiPipeSend; /* writting end */ | |||||
| //------------------------------------------ | |||||
| // argv[4] => UI Name | |||||
| argv[4] = host->uiName; | |||||
| //------------------------------------------ | |||||
| // argv[5] => NULL | |||||
| argv[5] = nullptr; | |||||
| //------------------------------------------ | |||||
| // fork | |||||
| int ret = -1; | |||||
| if ((! fork_exec(argv, &ret)) || ret == -1) | |||||
| { | |||||
| close(pipe1[0]); | |||||
| close(pipe1[1]); | |||||
| close(pipe2[0]); | |||||
| close(pipe2[1]); | |||||
| fail("fork_exec() failed"); | |||||
| return; | |||||
| } | |||||
| pid = ret; | |||||
| /* fork duplicated the handles, close pipe ends that are used by the child process */ | |||||
| close(pipe1[0]); | |||||
| close(pipe2[1]); | |||||
| pipeSend = pipe1[1]; /* [1] means writting end */ | |||||
| pipeRecv = pipe2[0]; /* [0] means reading end */ | |||||
| fcntl(pipeRecv, F_SETFL, fcntl(pipeRecv, F_GETFL) | O_NONBLOCK); | |||||
| //------------------------------------------ | |||||
| // wait a while for child process to confirm it is alive | |||||
| char ch; | |||||
| for (int i=0; ;) | |||||
| { | |||||
| ret = read(pipeRecv, &ch, 1); | |||||
| switch (ret) | |||||
| { | |||||
| case -1: | |||||
| if (errno == EAGAIN) | |||||
| { | |||||
| if (i < WAIT_START_TIMEOUT / WAIT_STEP) | |||||
| { | |||||
| carla_msleep(WAIT_STEP); | |||||
| i++; | |||||
| continue; | |||||
| } | |||||
| carla_stderr("we have waited for child with pid %d to appear for %.1f seconds and we are giving up", (int)pid, (float)WAIT_START_TIMEOUT / 1000.0f); | |||||
| } | |||||
| else | |||||
| carla_stderr("read() failed: %s", strerror(errno)); | |||||
| break; | |||||
| case 1: | |||||
| if (ch == '\n') | |||||
| // success | |||||
| return; | |||||
| carla_stderr("read() wrong first char '%c'", ch); | |||||
| break; | |||||
| default: | |||||
| carla_stderr("read() returned %d", ret); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| } | |||||
| carla_stderr("force killing misbehaved child %d (start)", (int)pid); | |||||
| if (kill(pid, SIGKILL) == -1) | |||||
| { | |||||
| carla_stderr("kill() failed: %s (start)\n", strerror(errno)); | |||||
| } | |||||
| /* wait a while child to exit, we dont like zombie processes */ | |||||
| wait_child(pid); | |||||
| #endif | |||||
| } | |||||
| void fail(const char* const error) | |||||
| { | |||||
| carla_stderr2(error); | |||||
| fHost->dispatcher(fHost->handle, HOST_OPCODE_UI_UNAVAILABLE, 0, 0, nullptr, 0.0f); | |||||
| } | |||||
| static bool fork_exec(const char* const argv[6], int* const retp) | |||||
| { | |||||
| int ret = *retp = vfork(); | |||||
| switch (ret) | |||||
| { | |||||
| case 0: /* child process */ | |||||
| execvp(argv[0], (char* const*)argv); | |||||
| carla_stderr2("exec of UI failed: %s", strerror(errno)); | |||||
| return false; | |||||
| case -1: | |||||
| carla_stderr2("fork() failed to create new process for plugin UI"); | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static bool wait_child(pid_t pid) | |||||
| { | |||||
| pid_t ret; | |||||
| int i; | |||||
| if (pid == -1) | |||||
| { | |||||
| carla_stderr2("Can't wait for pid -1"); | |||||
| return false; | |||||
| } | |||||
| for (i = 0; i < WAIT_ZOMBIE_TIMEOUT / WAIT_STEP; ++i) | |||||
| { | |||||
| ret = waitpid(pid, NULL, WNOHANG); | |||||
| if (ret != 0) | |||||
| { | |||||
| if (ret == pid) | |||||
| { | |||||
| //printf("child zombie with pid %d was consumed.\n", (int)pid); | |||||
| return true; | |||||
| } | |||||
| if (ret == -1) | |||||
| { | |||||
| carla_stderr2("waitpid(%d) failed: %s", (int)pid, strerror(errno)); | |||||
| return false; | |||||
| } | |||||
| carla_stderr2("we have waited for child pid %d to exit but we got pid %d instead", (int)pid, (int)ret); | |||||
| return false; | |||||
| } | |||||
| carla_msleep(WAIT_STEP); /* wait 100 ms */ | |||||
| } | |||||
| carla_stderr2("we have waited for child with pid %d to exit for %.1f seconds and we are giving up", (int)pid, (float)WAIT_START_TIMEOUT / 1000.0f); | |||||
| return false; | |||||
| } | |||||
| char* read_line() const | |||||
| { | |||||
| char ch; | |||||
| ssize_t ret; | |||||
| char buf[0xff]; | |||||
| char* ptr = buf; | |||||
| for (int i=0; i < 0xff; ++i) | |||||
| { | |||||
| ret = read(pipeRecv, &ch, 1); | |||||
| if (ret == 1 && ch != '\n') | |||||
| { | |||||
| if (ch == '\r') | |||||
| ch = '\n'; | |||||
| *ptr++ = ch; | |||||
| continue; | |||||
| } | |||||
| if (ptr != buf) | |||||
| { | |||||
| *ptr = '\0'; | |||||
| return strdup(buf); | |||||
| } | |||||
| break; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| ~UICarla() | |||||
| { | |||||
| printf("UI CARLA HERE 00END\n"); | |||||
| #ifdef DISTRHO_UI_EXTERNAL | |||||
| write(pipeSend, "quit\n", 5); | |||||
| /* for a while wait child to exit, we dont like zombie processes */ | |||||
| if (! wait_child(pid)) | |||||
| { | |||||
| carla_stderr2("force killing misbehaved child %d (exit)", (int)pid); | |||||
| if (kill(pid, SIGKILL) == -1) | |||||
| carla_stderr2("kill() failed: %s (exit)", strerror(errno)); | |||||
| else | |||||
| wait_child(pid); | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| // --------------------------------------------- | // --------------------------------------------- | ||||
| void carla_show(const bool yesNo) | void carla_show(const bool yesNo) | ||||
| { | { | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| glWindow.setVisible(yesNo); | glWindow.setVisible(yesNo); | ||||
| #else | |||||
| if (yesNo) | |||||
| write(pipeSend, "show\n", 5); | |||||
| else | |||||
| write(pipeSend, "hide\n", 5); | |||||
| #endif | |||||
| } | } | ||||
| void carla_idle() | void carla_idle() | ||||
| { | { | ||||
| fUi.idle(); | fUi.idle(); | ||||
| #if 1//def DISTRHO_UI_EXTERNAL | |||||
| char* locale = strdup(setlocale(LC_NUMERIC, nullptr)); | |||||
| setlocale(LC_NUMERIC, "POSIX"); | |||||
| for (;;) | |||||
| { | |||||
| char* const msg = read_line(); | |||||
| if (msg == nullptr) | |||||
| break; | |||||
| if (std::strcmp(msg, "control") == 0) | |||||
| { | |||||
| int index; | |||||
| float value; | |||||
| char* indexStr = read_line(); | |||||
| char* valueStr = read_line(); | |||||
| index = atoi(indexStr); | |||||
| if (sscanf(valueStr, "%f", &value) == 1) | |||||
| fHost->ui_parameter_changed(fHost->handle, index, value); | |||||
| else | |||||
| fprintf(stderr, "failed to convert \"%s\" to float\n", valueStr); | |||||
| carla_stdout("PARAM CHANGE, %i %f", index, value); | |||||
| std::free(indexStr); | |||||
| std::free(valueStr); | |||||
| } | |||||
| else if (std::strcmp(msg, "configure") == 0) | |||||
| { | |||||
| char* const key = read_line(); | |||||
| char* const value = read_line(); | |||||
| fHost->ui_custom_data_changed(fHost->handle, key, value); | |||||
| carla_stdout("STATE CHANGE, \"%s\" \"%s\"", key, value); | |||||
| std::free(key); | |||||
| std::free(value); | |||||
| } | |||||
| else if (std::strcmp(msg, "exiting") == 0) | |||||
| { | |||||
| /* for a while wait child to exit, we dont like zombie processes */ | |||||
| if (! wait_child(pid)) | |||||
| { | |||||
| fprintf(stderr, "force killing misbehaved child %d (exit)\n", (int)pid); | |||||
| if (kill(pid, SIGKILL) == -1) | |||||
| fprintf(stderr, "kill() failed: %s (exit)\n", strerror(errno)); | |||||
| else | |||||
| wait_child(pid); | |||||
| } | |||||
| fHost->ui_closed(fHost->handle); | |||||
| } | |||||
| else | |||||
| { | |||||
| carla_stderr("unknown message HOST: \"%s\"", msg); | |||||
| } | |||||
| std::free(msg); | |||||
| } | |||||
| setlocale(LC_NUMERIC, locale); | |||||
| std::free(locale); | |||||
| #endif | |||||
| } | } | ||||
| void carla_setParameterValue(const uint32_t index, const float value) | void carla_setParameterValue(const uint32_t index, const float value) | ||||
| { | { | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| fUi.parameterChanged(index, value); | fUi.parameterChanged(index, value); | ||||
| #else | |||||
| char msgParamIndex[0xff+1]; | |||||
| char msgParamValue[0xff+1]; | |||||
| std::snprintf(msgParamIndex, 0xff, "%d", index); | |||||
| std::snprintf(msgParamValue, 0xff, "%f", value); | |||||
| msgParamIndex[0xff] = '\0'; | |||||
| msgParamValue[0xff] = '\0'; | |||||
| write(pipeSend, "control\n", 8); | |||||
| write(pipeSend, msgParamIndex, std::strlen(msgParamIndex)); | |||||
| write(pipeSend, msgParamValue, std::strlen(msgParamValue)); | |||||
| #endif | |||||
| } | } | ||||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||||
| void carla_setMidiProgram(const uint32_t realProgram) | void carla_setMidiProgram(const uint32_t realProgram) | ||||
| { | { | ||||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| fUi.programChanged(realProgram); | fUi.programChanged(realProgram); | ||||
| #else | |||||
| return; | |||||
| // unused | |||||
| (void)realProgram; | |||||
| #endif | |||||
| #else | |||||
| char msgProgram[0xff+1]; | |||||
| std::snprintf(msgProgram, 0xff, "%d", realProgram); | |||||
| msgProgram[0xff] = '\0'; | |||||
| write(pipeSend, "program\n", 8); | |||||
| write(pipeSend, msgProgram, std::strlen(msgProgram)); | |||||
| #endif | |||||
| } | } | ||||
| #endif | |||||
| #if DISTRHO_PLUGIN_WANT_STATE | |||||
| void carla_setCustomData(const char* const key, const char* const value) | void carla_setCustomData(const char* const key, const char* const value) | ||||
| { | { | ||||
| #if DISTRHO_PLUGIN_WANT_STATE | |||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| fUi.stateChanged(key, value); | fUi.stateChanged(key, value); | ||||
| #else | |||||
| return; | |||||
| // unused | |||||
| (void)key; | |||||
| (void)value; | |||||
| #endif | |||||
| #else | |||||
| CarlaString skey(key), svalue(value); | |||||
| skey.replace('\n', '\r'); | |||||
| svalue.replace('\n', '\r'); | |||||
| write(pipeSend, "configure\n", 10); | |||||
| write(pipeSend, (const char*)skey, skey.length()); | |||||
| write(pipeSend, (const char*)svalue, svalue.length()); | |||||
| #endif | |||||
| } | } | ||||
| #endif | |||||
| void carla_setUiTitle(const char* const uiName) | |||||
| void carla_setUiTitle(const char* const uiTitle) | |||||
| { | { | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| glWindow.setWindowTitle(uiName); | glWindow.setWindowTitle(uiName); | ||||
| #else | |||||
| CarlaString stitle(uiTitle); | |||||
| stitle.replace('\n', '\r'); | |||||
| write(pipeSend, "uiTitle\n", 8); | |||||
| write(pipeSend, (const char*)stitle, stitle.length()); | |||||
| #endif | |||||
| } | } | ||||
| // --------------------------------------------- | // --------------------------------------------- | ||||
| protected: | protected: | ||||
| void editParameter(uint32_t, bool) | |||||
| void handleEditParameter(uint32_t, bool) | |||||
| { | { | ||||
| // TODO | // TODO | ||||
| } | } | ||||
| void setParameterValue(uint32_t rindex, float value) | |||||
| void handleSetParameterValue(uint32_t rindex, float value) | |||||
| { | { | ||||
| fHost->ui_parameter_changed(fHost->handle, rindex, value); | fHost->ui_parameter_changed(fHost->handle, rindex, value); | ||||
| } | } | ||||
| void setState(const char* key, const char* value) | |||||
| void handleSetState(const char* key, const char* value) | |||||
| { | { | ||||
| fHost->ui_custom_data_changed(fHost->handle, key, value); | fHost->ui_custom_data_changed(fHost->handle, key, value); | ||||
| } | } | ||||
| void sendNote(bool, uint8_t, uint8_t, uint8_t) | |||||
| void handleSendNote(bool, uint8_t, uint8_t, uint8_t) | |||||
| { | { | ||||
| // TODO | // TODO | ||||
| } | } | ||||
| void uiResize(unsigned int /*width*/, unsigned int /*height*/) | |||||
| void handleUiResize(unsigned int /*width*/, unsigned int /*height*/) | |||||
| { | { | ||||
| // TODO | // TODO | ||||
| } | } | ||||
| @@ -141,41 +534,63 @@ private: | |||||
| // UI | // UI | ||||
| UIInternal fUi; | UIInternal fUi; | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| // OpenGL stuff | // OpenGL stuff | ||||
| App& glApp; | App& glApp; | ||||
| Window& glWindow; | Window& glWindow; | ||||
| #else | |||||
| int pipeRecv; /* the pipe end that is used for receiving messages from UI */ | |||||
| int pipeSend; /* the pipe end that is used for sending messages to UI */ | |||||
| pid_t pid; | |||||
| #endif | |||||
| // --------------------------------------------- | // --------------------------------------------- | ||||
| // Callbacks | // Callbacks | ||||
| #ifdef DISTRHO_UI_OPENGL | |||||
| #define handlePtr ((UICarla*)ptr) | #define handlePtr ((UICarla*)ptr) | ||||
| static void editParameterCallback(void* ptr, uint32_t index, bool started) | static void editParameterCallback(void* ptr, uint32_t index, bool started) | ||||
| { | { | ||||
| handlePtr->editParameter(index, started); | |||||
| handlePtr->handleEditParameter(index, started); | |||||
| } | } | ||||
| static void setParameterCallback(void* ptr, uint32_t rindex, float value) | static void setParameterCallback(void* ptr, uint32_t rindex, float value) | ||||
| { | { | ||||
| handlePtr->setParameterValue(rindex, value); | |||||
| handlePtr->handleSetParameterValue(rindex, value); | |||||
| } | } | ||||
| #if DISTRHO_PLUGIN_WANT_STATE | |||||
| static void setStateCallback(void* ptr, const char* key, const char* value) | static void setStateCallback(void* ptr, const char* key, const char* value) | ||||
| { | { | ||||
| handlePtr->setState(key, value); | |||||
| handlePtr->handleSetState(key, value); | |||||
| } | } | ||||
| #else | |||||
| static constexpr setStateFunc setStateCallback = nullptr; | |||||
| #endif | |||||
| #if DISTRHO_PLUGIN_IS_SYNTH | |||||
| static void sendNoteCallback(void* ptr, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity) | static void sendNoteCallback(void* ptr, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity) | ||||
| { | { | ||||
| handlePtr->sendNote(onOff, channel, note, velocity); | |||||
| handlePtr->handleSendNote(onOff, channel, note, velocity); | |||||
| } | } | ||||
| #else | |||||
| static constexpr sendNoteFunc sendNoteCallback = nullptr; | |||||
| #endif | |||||
| static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height) | static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height) | ||||
| { | { | ||||
| handlePtr->uiResize(width, height); | |||||
| handlePtr->handleUiResize(width, height); | |||||
| } | } | ||||
| #undef handlePtr | #undef handlePtr | ||||
| #else | |||||
| static constexpr editParamFunc editParameterCallback = nullptr; | |||||
| static constexpr setParamFunc setParameterCallback = nullptr; | |||||
| static constexpr setStateFunc setStateCallback = nullptr; | |||||
| static constexpr sendNoteFunc sendNoteCallback = nullptr; | |||||
| static constexpr uiResizeFunc uiResizeCallback = nullptr; | |||||
| #endif | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UICarla) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UICarla) | ||||
| }; | }; | ||||
| @@ -405,11 +405,13 @@ nekoui_instantiate( | |||||
| if (pipe(pipe1) != 0) | if (pipe(pipe1) != 0) | ||||
| { | { | ||||
| fprintf(stderr, "pipe1 creation failed.\n"); | fprintf(stderr, "pipe1 creation failed.\n"); | ||||
| goto fail_free_control; | |||||
| } | } | ||||
| if (pipe(pipe2) != 0) | if (pipe(pipe2) != 0) | ||||
| { | { | ||||
| fprintf(stderr, "pipe2 creation failed.\n"); | fprintf(stderr, "pipe2 creation failed.\n"); | ||||
| goto fail_free_control; | |||||
| } | } | ||||
| snprintf(ui_recv_pipe, sizeof(ui_recv_pipe), "%d", pipe1[0]); /* [0] means reading end */ | snprintf(ui_recv_pipe, sizeof(ui_recv_pipe), "%d", pipe1[0]); /* [0] means reading end */ | ||||
| @@ -0,0 +1,116 @@ | |||||
| /* | |||||
| * DISTRHO Notes Plugin | |||||
| * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| * | |||||
| * This program is free software; you can redistribute it and/or | |||||
| * modify it under the terms of the GNU General Public License as | |||||
| * published by the Free Software Foundation; either version 2 of | |||||
| * the License, or any later version. | |||||
| * | |||||
| * This program is distributed in the hope that it will be useful, | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| * GNU General Public License for more details. | |||||
| * | |||||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
| */ | |||||
| #include "DistrhoPluginNotes.hpp" | |||||
| #include <cmath> | |||||
| START_NAMESPACE_DISTRHO | |||||
| // ----------------------------------------------------------------------- | |||||
| DistrhoPluginNotes::DistrhoPluginNotes() | |||||
| : Plugin(1, 0, 103) // 1 parameter, 0 programs, 103 states | |||||
| { | |||||
| fCurPage = 0; | |||||
| } | |||||
| DistrhoPluginNotes::~DistrhoPluginNotes() | |||||
| { | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| // Init | |||||
| void DistrhoPluginNotes::d_initParameter(uint32_t index, Parameter& parameter) | |||||
| { | |||||
| if (index != 0) | |||||
| return; | |||||
| parameter.hints = PARAMETER_IS_AUTOMABLE | PARAMETER_IS_INTEGER; | |||||
| parameter.name = "Page"; | |||||
| parameter.symbol = "page"; | |||||
| parameter.ranges.def = 1; | |||||
| parameter.ranges.min = 1; | |||||
| parameter.ranges.max = 100; | |||||
| parameter.ranges.step = 1; | |||||
| parameter.ranges.stepSmall = 1; | |||||
| parameter.ranges.stepLarge = 10; | |||||
| } | |||||
| void DistrhoPluginNotes::d_initStateKey(uint32_t index, d_string& stateKey) | |||||
| { | |||||
| switch (index) | |||||
| { | |||||
| case 0: | |||||
| stateKey = "readOnly"; | |||||
| break; | |||||
| case 1 ... 100: | |||||
| stateKey = "pageText #" + d_string(index); | |||||
| break; | |||||
| case 101: | |||||
| stateKey = "guiWidth"; | |||||
| break; | |||||
| case 102: | |||||
| stateKey = "guiHeight"; | |||||
| break; | |||||
| } | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| // Internal data | |||||
| float DistrhoPluginNotes::d_getParameterValue(uint32_t index) const | |||||
| { | |||||
| if (index != 0) | |||||
| return 0.0f; | |||||
| return fCurPage; | |||||
| } | |||||
| void DistrhoPluginNotes::d_setParameterValue(uint32_t index, float value) | |||||
| { | |||||
| if (index != 0) | |||||
| return; | |||||
| fCurPage = int(value); | |||||
| } | |||||
| void DistrhoPluginNotes::d_setState(const char*, const char*) | |||||
| { | |||||
| // do nothing, used only for UI state | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| // Process | |||||
| void DistrhoPluginNotes::d_run(float** inputs, float** outputs, uint32_t frames, const MidiEvent*, uint32_t) | |||||
| { | |||||
| std::memcpy(outputs[0], inputs[0], sizeof(float)*frames); | |||||
| std::memcpy(outputs[1], inputs[1], sizeof(float)*frames); | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| Plugin* createPlugin() | |||||
| { | |||||
| return new DistrhoPluginNotes(); | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| END_NAMESPACE_DISTRHO | |||||
| @@ -0,0 +1,90 @@ | |||||
| /* | |||||
| * DISTRHO Notes Plugin | |||||
| * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| * | |||||
| * This program is free software; you can redistribute it and/or | |||||
| * modify it under the terms of the GNU General Public License as | |||||
| * published by the Free Software Foundation; either version 2 of | |||||
| * the License, or any later version. | |||||
| * | |||||
| * This program is distributed in the hope that it will be useful, | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| * GNU General Public License for more details. | |||||
| * | |||||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
| */ | |||||
| #ifndef DISTRHO_PLUGIN_NOTES_HPP_INCLUDED | |||||
| #define DISTRHO_PLUGIN_NOTES_HPP_INCLUDED | |||||
| #include "DistrhoPlugin.hpp" | |||||
| START_NAMESPACE_DISTRHO | |||||
| // ----------------------------------------------------------------------- | |||||
| class DistrhoPluginNotes : public Plugin | |||||
| { | |||||
| public: | |||||
| DistrhoPluginNotes(); | |||||
| ~DistrhoPluginNotes() override; | |||||
| protected: | |||||
| // ------------------------------------------------------------------- | |||||
| // Information | |||||
| const char* d_getLabel() const noexcept override | |||||
| { | |||||
| return "Notes"; | |||||
| } | |||||
| const char* d_getMaker() const noexcept override | |||||
| { | |||||
| return "DISTRHO"; | |||||
| } | |||||
| const char* d_getLicense() const noexcept override | |||||
| { | |||||
| return "GPL v2+"; | |||||
| } | |||||
| uint32_t d_getVersion() const noexcept override | |||||
| { | |||||
| return 0x1000; | |||||
| } | |||||
| long d_getUniqueId() const noexcept override | |||||
| { | |||||
| return d_cconst('D', 'N', 'o', 't'); | |||||
| } | |||||
| // ------------------------------------------------------------------- | |||||
| // Init | |||||
| void d_initParameter(uint32_t index, Parameter& parameter) override; | |||||
| void d_initStateKey(uint32_t index, d_string& stateKeyName) override; | |||||
| // ------------------------------------------------------------------- | |||||
| // Internal data | |||||
| float d_getParameterValue(uint32_t index) const override; | |||||
| void d_setParameterValue(uint32_t index, float value) override; | |||||
| void d_setState(const char* key, const char* value) override; | |||||
| // ------------------------------------------------------------------- | |||||
| // Process | |||||
| void d_run(float** inputs, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount) override; | |||||
| // ------------------------------------------------------------------- | |||||
| private: | |||||
| int fCurPage; | |||||
| }; | |||||
| // ----------------------------------------------------------------------- | |||||
| END_NAMESPACE_DISTRHO | |||||
| #endif // DISTRHO_PLUGIN_NOTES_HPP_INCLUDED | |||||
| @@ -0,0 +1,38 @@ | |||||
| /* | |||||
| * DISTRHO Notes Plugin | |||||
| * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| * | |||||
| * This program is free software; you can redistribute it and/or | |||||
| * modify it under the terms of the GNU General Public License as | |||||
| * published by the Free Software Foundation; either version 2 of | |||||
| * the License, or any later version. | |||||
| * | |||||
| * This program is distributed in the hope that it will be useful, | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| * GNU General Public License for more details. | |||||
| * | |||||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
| */ | |||||
| #include "DistrhoUINotes.hpp" | |||||
| START_NAMESPACE_DISTRHO | |||||
| // ----------------------------------------------------------------------- | |||||
| DistrhoUINotes::DistrhoUINotes() | |||||
| : ExternalUI() | |||||
| { | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| UI* createUI() | |||||
| { | |||||
| return new DistrhoUINotes(); | |||||
| } | |||||
| // ----------------------------------------------------------------------- | |||||
| END_NAMESPACE_DISTRHO | |||||
| @@ -0,0 +1,48 @@ | |||||
| /* | |||||
| * DISTRHO Notes Plugin | |||||
| * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| * | |||||
| * This program is free software; you can redistribute it and/or | |||||
| * modify it under the terms of the GNU General Public License as | |||||
| * published by the Free Software Foundation; either version 2 of | |||||
| * the License, or any later version. | |||||
| * | |||||
| * This program is distributed in the hope that it will be useful, | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| * GNU General Public License for more details. | |||||
| * | |||||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
| */ | |||||
| #ifndef DISTRHO_UI_NOTES_HPP_INCLUDED | |||||
| #define DISTRHO_UI_NOTES_HPP_INCLUDED | |||||
| #include "DistrhoUIExternal.hpp" | |||||
| START_NAMESPACE_DISTRHO | |||||
| // ----------------------------------------------------------------------- | |||||
| class DistrhoUINotes : public ExternalUI | |||||
| { | |||||
| public: | |||||
| DistrhoUINotes(); | |||||
| protected: | |||||
| // ------------------------------------------------------------------- | |||||
| // Information (External) | |||||
| const char* d_getExternalFilename() const override | |||||
| { | |||||
| return "notes-ui"; | |||||
| } | |||||
| // ------------------------------------------------------------------- | |||||
| }; | |||||
| // ----------------------------------------------------------------------- | |||||
| END_NAMESPACE_DISTRHO | |||||
| #endif // DISTRHO_UI_NOTES_HPP_INCLUDED | |||||
| @@ -0,0 +1,185 @@ | |||||
| #!/usr/bin/env python3 | |||||
| # -*- coding: utf-8 -*- | |||||
| # DISTRHO Plugin Toolkit (DPT) | |||||
| # Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| # | |||||
| # Permission to use, copy, modify, and/or distribute this software for any purpose with | |||||
| # or without fee is hereby granted, provided that the above copyright notice and this | |||||
| # permission notice appear in all copies. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| # TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||||
| # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||||
| # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||||
| # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||||
| # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||||
| # ----------------------------------------------------------------------- | |||||
| # Imports | |||||
| from os import fdopen, O_NONBLOCK | |||||
| from fcntl import fcntl, F_GETFL, F_SETFL | |||||
| from sys import argv | |||||
| # ----------------------------------------------------------------------- | |||||
| # External UI | |||||
| class ExternalUI(object): | |||||
| def __init__(self): | |||||
| object.__init__(self) | |||||
| self.fPipeRecv = None | |||||
| self.fPipeSend = None | |||||
| self.fQuitReceived = False | |||||
| self.fSampleRate = float(argv[1]) | |||||
| self.fPipeRecvFd = int(argv[2]) | |||||
| self.fPipeSendFd = int(argv[3]) | |||||
| self.fUiName = argv[4] | |||||
| fcntl(self.fPipeRecvFd, F_SETFL, fcntl(self.fPipeRecvFd, F_GETFL) | O_NONBLOCK) | |||||
| self.fPipeRecv = fdopen(self.fPipeRecvFd, 'r') | |||||
| self.fPipeSend = fdopen(self.fPipeSendFd, 'w') | |||||
| # send empty line (just newline char) | |||||
| self.send([""]) | |||||
| # ------------------------------------------------------------------- | |||||
| # Host DSP State | |||||
| def d_getSampleRate(self): | |||||
| return self.fSampleRate | |||||
| def d_editParameter(self, index, started): | |||||
| self.send(["editParam", index, started]) | |||||
| def d_setParameterValue(self, index, value): | |||||
| self.send(["control", index, value]) | |||||
| def d_setState(self, key, value): | |||||
| self.send(["configure", key, value]) | |||||
| def d_sendNote(self, onOff, channel, note, velocity): | |||||
| self.send(["note", onOff, channel, note, velocity]) | |||||
| # ------------------------------------------------------------------- | |||||
| # DSP Callbacks | |||||
| def d_parameterChanged(self, index, value): | |||||
| return | |||||
| def d_programChanged(self, index): | |||||
| return | |||||
| def d_stateChanged(self, key, value): | |||||
| return | |||||
| def d_noteReceived(self, onOff, channel, note, velocity): | |||||
| return | |||||
| # ------------------------------------------------------------------- | |||||
| # ExternalUI Callbacks | |||||
| def d_uiShow(self): | |||||
| return | |||||
| def d_uiHide(self): | |||||
| return | |||||
| def d_uiQuit(self): | |||||
| return | |||||
| def d_uiTitleChanged(self, uiTitle): | |||||
| return | |||||
| # ------------------------------------------------------------------- | |||||
| # Public methods | |||||
| def closeExternalUI(self): | |||||
| if not self.fQuitReceived: | |||||
| self.send(["exiting"]) | |||||
| if self.fPipeRecv is not None: | |||||
| self.fPipeRecv.close() | |||||
| self.fPipeRecv = None | |||||
| if self.fPipeSend is not None: | |||||
| self.fPipeSend.close() | |||||
| self.fPipeSend = None | |||||
| def idleExternalUI(self): | |||||
| if self.fPipeRecv is None: | |||||
| return False | |||||
| try: | |||||
| msg = self.fPipeRecv.readline().strip() | |||||
| except IOError: | |||||
| return False | |||||
| if msg == "": | |||||
| return True | |||||
| if msg == "control": | |||||
| index = int(self.fPipeRecv.readline()) | |||||
| value = float(self.fPipeRecv.readline()) | |||||
| self.d_parameterChanged(index, value) | |||||
| elif msg == "program": | |||||
| index = int(self.fPipeRecv.readline()) | |||||
| self.d_programChanged(index) | |||||
| elif msg == "configure": | |||||
| key = self.fPipeRecv.readline().strip().replace("\r", "\n") | |||||
| value = self.fPipeRecv.readline().strip().replace("\r", "\n") | |||||
| self.d_stateChanged(key, value) | |||||
| elif msg == "note": | |||||
| onOff = bool(self.fPipeRecv.readline().strip() == "true") | |||||
| channel = int(self.fPipeRecv.readline()) | |||||
| note = int(self.fPipeRecv.readline()) | |||||
| velocity = int(self.fPipeRecv.readline()) | |||||
| self.d_noteReceived(onOff, channel, note, velocity) | |||||
| elif msg == "show": | |||||
| self.d_uiShow() | |||||
| elif msg == "hide": | |||||
| self.d_uiHide() | |||||
| elif msg == "quit": | |||||
| self.fQuitReceived = True | |||||
| self.d_uiQuit() | |||||
| elif msg == "uiTitle": | |||||
| uiTitle = self.fPipeRecv.readline().strip().replace("\r", "\n") | |||||
| self.d_uiTitleChanged(uiTitle) | |||||
| else: | |||||
| print("unknown message: \"" + msg + "\"") | |||||
| return True | |||||
| # ------------------------------------------------------------------- | |||||
| # Internal stuff | |||||
| def send(self, lines): | |||||
| if self.fPipeSend is None: | |||||
| return | |||||
| for line in lines: | |||||
| if isinstance(line, str): | |||||
| line2 = line.replace("\n", "\r") | |||||
| else: | |||||
| if isinstance(line, bool): | |||||
| line2 = "true" if line else "false" | |||||
| elif isinstance(line, int): | |||||
| line2 = "%i" % line | |||||
| elif isinstance(line, float): | |||||
| line2 = "%.10f" % line | |||||
| else: | |||||
| return | |||||
| self.fPipeSend.write(line2 + "\n") | |||||
| self.fPipeSend.flush() | |||||
| @@ -0,0 +1,233 @@ | |||||
| #!/usr/bin/env python3 | |||||
| # -*- coding: utf-8 -*- | |||||
| # DISTRHO Notes Plugin | |||||
| # Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||||
| # | |||||
| # This program is free software; you can redistribute it and/or | |||||
| # modify it under the terms of the GNU General Public License as | |||||
| # published by the Free Software Foundation; either version 2 of | |||||
| # the License, or any later version. | |||||
| # | |||||
| # This program is distributed in the hope that it will be useful, | |||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| # GNU General Public License for more details. | |||||
| # | |||||
| # For a full copy of the GNU General Public License see the GPL.txt file | |||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| # Imports (Global) | |||||
| from numpy import rint | |||||
| from sys import argv, exit | |||||
| try: | |||||
| from PyQt5.QtCore import pyqtSlot | |||||
| from PyQt5.QtWidgets import QApplication, QGridLayout, QLabel, QPushButton, QTextEdit, QWidget | |||||
| except: | |||||
| from PyQt4.QtCore import pyqtSlot | |||||
| from PyQt4.QtGui import QApplication, QGridLayout, QLabel, QPushButton, QTextEdit, QWidget | |||||
| # ----------------------------------------------------------------------- | |||||
| # Imports (ExternalUI) | |||||
| from externalui import ExternalUI | |||||
| from paramspinbox import ParamSpinBox | |||||
| # ----------------------------------------------------------------------- | |||||
| # External UI | |||||
| class DistrhoUINotes(QWidget, ExternalUI): | |||||
| def __init__(self): | |||||
| QWidget.__init__(self, None) | |||||
| ExternalUI.__init__(self) | |||||
| self.fCurPage = 1 | |||||
| self.fSaveSizeNowChecker = -1 | |||||
| self.fSaveTextNowChecker = -1 | |||||
| self.fNotes = ["" for x in range(100)] | |||||
| self.fTextEdit = QTextEdit(self) | |||||
| self.fButton = QPushButton(self) | |||||
| self.fProgressBar = ParamSpinBox(self) | |||||
| self.fSpacer = QLabel(self) | |||||
| self.fGridLayout = QGridLayout(self) | |||||
| self.fButton.setCheckable(True) | |||||
| self.fButton.setChecked(True) | |||||
| self.fButton.setText("Edit") | |||||
| self.fButton.setFixedSize(self.fButton.minimumSizeHint()) | |||||
| self.fProgressBar.setMinimum(1.0) | |||||
| self.fProgressBar.setMaximum(100.0) | |||||
| self.fProgressBar.setValue(1.0) | |||||
| self.fProgressBar.setStep(1.0) | |||||
| self.fProgressBar.setStepSmall(1.0) | |||||
| self.fProgressBar.setStepLarge(10.0) | |||||
| self.fSpacer.setText("") | |||||
| self.fSpacer.setFixedSize(5, 5) | |||||
| self.fTextEdit.setReadOnly(False) | |||||
| self.setLayout(self.fGridLayout) | |||||
| self.fGridLayout.addWidget(self.fTextEdit, 0, 0, 1, 3) | |||||
| self.fGridLayout.addWidget(self.fButton, 1, 0, 1, 1) | |||||
| self.fGridLayout.addWidget(self.fProgressBar, 1, 1, 1, 1) | |||||
| self.fGridLayout.addWidget(self.fSpacer, 1, 2, 1, 1) | |||||
| self.fGridLayout.setContentsMargins(0, 0, 0, 0) | |||||
| self.resize(300, 200) | |||||
| self.setWindowTitle(self.fUiName) | |||||
| self.fButton.clicked.connect(self.slot_buttonClicked) | |||||
| self.fProgressBar.valueChanged.connect(self.slot_progressBarValueChanged) | |||||
| self.fTextEdit.textChanged.connect(self.slot_textChanged) | |||||
| self.fIdleTimer = self.startTimer(50) | |||||
| def saveCurrentTextState(self): | |||||
| pageKey = "pageText %i" % self.fCurPage | |||||
| pageValue = self.fTextEdit.toPlainText() | |||||
| if pageValue != self.fNotes[self.fCurPage-1]: | |||||
| self.fNotes[self.fCurPage-1] = pageValue | |||||
| self.d_setState(pageKey, pageValue) | |||||
| # ------------------------------------------------------------------- | |||||
| @pyqtSlot(bool) | |||||
| def slot_buttonClicked(self, click): | |||||
| readOnly = not click | |||||
| self.fTextEdit.setReadOnly(readOnly) | |||||
| self.d_setState("readOnly", "yes" if readOnly else "no") | |||||
| @pyqtSlot(float) | |||||
| def slot_progressBarValueChanged(self, value): | |||||
| value = rint(value) | |||||
| if self.fCurPage == int(value): | |||||
| return | |||||
| # maybe save current text before changing page | |||||
| if self.fSaveTextNowChecker >= 0 and value >= 1.0 and value <= 100.0: | |||||
| self.saveCurrentTextState() | |||||
| self.fSaveTextNowChecker = -1 | |||||
| # change current page | |||||
| self.d_parameterChanged(0, value) | |||||
| # tell host about this change | |||||
| self.d_setParameterValue(0, value) | |||||
| @pyqtSlot() | |||||
| def slot_textChanged(self): | |||||
| self.fSaveTextNowChecker = 0 | |||||
| # ------------------------------------------------------------------- | |||||
| # DSP Callbacks | |||||
| def d_parameterChanged(self, index, value): | |||||
| if index != 0: | |||||
| return | |||||
| nextCurPage = int(value) | |||||
| if nextCurPage != self.fCurPage and nextCurPage >= 1 and nextCurPage <= 100: | |||||
| self.saveCurrentTextState() | |||||
| self.fCurPage = nextCurPage | |||||
| self.fTextEdit.setPlainText(self.fNotes[self.fCurPage-1]) | |||||
| self.fProgressBar.setValue(self.fCurPage) | |||||
| self.fProgressBar.update() | |||||
| def d_stateChanged(self, key, value): | |||||
| if key == "guiWidth": | |||||
| try: | |||||
| width = int(value) | |||||
| except: | |||||
| width = 0 | |||||
| if width > 0: | |||||
| self.resize(width, self.height()) | |||||
| elif key == "guiHeight": | |||||
| try: | |||||
| height = int(value) | |||||
| except: | |||||
| height = 0 | |||||
| if height > 0: | |||||
| self.resize(self.width(), height) | |||||
| elif key.startswith("pageText #"): | |||||
| try: | |||||
| pageIndex = int(key.replace("pageText #","")) | |||||
| except: | |||||
| pageIndex = 0 | |||||
| if pageIndex >= 1 and pageIndex <= 100: | |||||
| self.fNotes[pageIndex-1] = value | |||||
| if pageIndex == self.fCurPage: | |||||
| self.fTextEdit.setPlainText(self.fNotes[pageIndex-1]) | |||||
| elif key == "readOnly": | |||||
| readOnly = (value == "yes") | |||||
| self.fButton.setChecked(not readOnly) | |||||
| self.fTextEdit.setReadOnly(readOnly) | |||||
| # ------------------------------------------------------------------- | |||||
| # ExternalUI Callbacks | |||||
| def d_uiShow(self): | |||||
| self.show() | |||||
| def d_uiHide(self): | |||||
| self.hide() | |||||
| def d_uiQuit(self): | |||||
| self.close() | |||||
| app.quit() | |||||
| def d_uiTitleChanged(self, uiTitle): | |||||
| self.setWindowTitle(uiTitle) | |||||
| # ------------------------------------------------------------------- | |||||
| # Qt events | |||||
| def resizeEvent(self, event): | |||||
| self.fSaveSizeNowChecker = 0 | |||||
| QWidget.resizeEvent(self, event) | |||||
| def timerEvent(self, event): | |||||
| if event.timerId() == self.fIdleTimer: | |||||
| if self.fSaveSizeNowChecker == 11: | |||||
| self.d_setState("guiWidth", str(self.width())) | |||||
| self.d_setState("guiHeight", str(self.height())) | |||||
| self.fSaveSizeNowChecker = -1 | |||||
| elif self.fSaveSizeNowChecker >= 0: | |||||
| self.fSaveSizeNowChecker += 1 | |||||
| if self.fSaveTextNowChecker == 11: | |||||
| self.saveCurrentTextState() | |||||
| self.fSaveTextNowChecker = -1 | |||||
| elif self.fSaveTextNowChecker >= 0: | |||||
| self.fSaveTextNowChecker += 1 | |||||
| if not self.idleExternalUI(): | |||||
| self.d_uiQuit() | |||||
| QWidget.timerEvent(self, event) | |||||
| def closeEvent(self, event): | |||||
| self.closeExternalUI() | |||||
| QWidget.closeEvent(self, event) | |||||
| #--------------- main ------------------ | |||||
| if __name__ == '__main__': | |||||
| app = QApplication(argv) | |||||
| #app... | |||||
| gui = DistrhoUINotes() | |||||
| exit(app.exec_()) | |||||
| @@ -0,0 +1 @@ | |||||
| /home/falktx/Personal/FOSS/GIT/Carla/source/widgets/paramspinbox.py | |||||
| @@ -0,0 +1 @@ | |||||
| @@ -17,7 +17,7 @@ | |||||
| #ifndef DISTRHO_UI_INTERNAL_HPP_INCLUDED | #ifndef DISTRHO_UI_INTERNAL_HPP_INCLUDED | ||||
| #define DISTRHO_UI_INTERNAL_HPP_INCLUDED | #define DISTRHO_UI_INTERNAL_HPP_INCLUDED | ||||
| # include "../DistrhoUI.hpp" | |||||
| #include "../DistrhoUI.hpp" | |||||
| #if defined(DISTRHO_UI_EXTERNAL) | #if defined(DISTRHO_UI_EXTERNAL) | ||||
| # include "../DistrhoUIExternal.hpp" | # include "../DistrhoUIExternal.hpp" | ||||
| @@ -25,8 +25,10 @@ | |||||
| # include "../DistrhoUIOpenGL.hpp" | # include "../DistrhoUIOpenGL.hpp" | ||||
| # include "../dgl/App.hpp" | # include "../dgl/App.hpp" | ||||
| # include "../dgl/Window.hpp" | # include "../dgl/Window.hpp" | ||||
| #else | |||||
| #elif defined(DISTRHO_UI_QT) | |||||
| # include "../DistrhoUIQt.hpp" | # include "../DistrhoUIQt.hpp" | ||||
| #else | |||||
| # error Invalid UI type | |||||
| #endif | #endif | ||||
| #include <cassert> | #include <cassert> | ||||
| @@ -129,19 +131,20 @@ public: | |||||
| if (fUi == nullptr) | if (fUi == nullptr) | ||||
| return; | return; | ||||
| #ifdef DISTRHO_UI_QT | |||||
| assert(winId == 0); | |||||
| if (winId != 0) | |||||
| return; | |||||
| #endif | |||||
| fData->ptr = ptr; | fData->ptr = ptr; | ||||
| fData->editParamCallbackFunc = editParamCall; | fData->editParamCallbackFunc = editParamCall; | ||||
| fData->setParamCallbackFunc = setParamCall; | fData->setParamCallbackFunc = setParamCall; | ||||
| fData->setStateCallbackFunc = setStateCall; | fData->setStateCallbackFunc = setStateCall; | ||||
| fData->sendNoteCallbackFunc = sendNoteCall; | fData->sendNoteCallbackFunc = sendNoteCall; | ||||
| fData->uiResizeCallbackFunc = uiResizeCall; | fData->uiResizeCallbackFunc = uiResizeCall; | ||||
| #ifndef DISTRHO_UI_OPENGL | |||||
| assert(winId == 0); | |||||
| return; | |||||
| // unused | |||||
| (void)winId; | |||||
| #endif | |||||
| } | } | ||||
| ~UIInternal() | ~UIInternal() | ||||
| @@ -225,7 +228,10 @@ public: | |||||
| } | } | ||||
| #if defined(DISTRHO_UI_EXTERNAL) | #if defined(DISTRHO_UI_EXTERNAL) | ||||
| // not needed | |||||
| const char* getExternalFilename() const | |||||
| { | |||||
| return ((ExternalUI*)fUi)->d_getExternalFilename(); | |||||
| } | |||||
| #elif defined(DISTRHO_UI_OPENGL) | #elif defined(DISTRHO_UI_OPENGL) | ||||
| DGL::App& getApp() | DGL::App& getApp() | ||||
| { | { | ||||
| @@ -237,21 +243,21 @@ public: | |||||
| return glWindow; | return glWindow; | ||||
| } | } | ||||
| intptr_t getWindowId() const | |||||
| /*intptr_t getWindowId() const | |||||
| { | { | ||||
| return glWindow.getWindowId(); | return glWindow.getWindowId(); | ||||
| } | |||||
| }*/ | |||||
| void fixWindowSize() | |||||
| /*void fixWindowSize() | |||||
| { | { | ||||
| assert(fUi != nullptr); | assert(fUi != nullptr); | ||||
| glWindow.setSize(fUi->d_getWidth(), fUi->d_getHeight()); | glWindow.setSize(fUi->d_getWidth(), fUi->d_getHeight()); | ||||
| } | |||||
| }*/ | |||||
| #elif defined(DISTRHO_UI_QT) | #elif defined(DISTRHO_UI_QT) | ||||
| QtUI* getQtUI() const | |||||
| /*QtUI* getQtUI() const | |||||
| { | { | ||||
| return (QtUI*)fUi; | return (QtUI*)fUi; | ||||
| } | |||||
| }*/ | |||||
| bool isResizable() const | bool isResizable() const | ||||
| { | { | ||||
| @@ -131,7 +131,7 @@ carla-native-plugin.cpp.o: carla-native-plugin.cpp carla-native-base.cpp ../modu | |||||
| carla-native-export: carla-native-export.cpp.o $(LIBS) | carla-native-export: carla-native-export.cpp.o $(LIBS) | ||||
| $(CXX) $^ $(LINK_FLAGS) -o $@ | $(CXX) $^ $(LINK_FLAGS) -o $@ | ||||
| ./carla-native-export | |||||
| # ./carla-native-export | |||||
| carla-native-export.exe: carla-native-export.cpp.o $(LIBS) | carla-native-export.exe: carla-native-export.cpp.o $(LIBS) | ||||
| $(CXX) $^ $(LINK_FLAGS) -o $@ | $(CXX) $^ $(LINK_FLAGS) -o $@ | ||||
| @@ -56,8 +56,8 @@ void carla_register_native_plugin_PingPongPan(); | |||||
| // void carla_register_native_plugin_StereoEnhancer(); | // void carla_register_native_plugin_StereoEnhancer(); | ||||
| #endif | #endif | ||||
| // DISTRHO plugins (Qt) | |||||
| // void carla_register_native_plugin_Notes(); | |||||
| // DISTRHO plugins (PyQt) | |||||
| void carla_register_native_plugin_Notes(); | |||||
| #ifdef WANT_ZYNADDSUBFX | #ifdef WANT_ZYNADDSUBFX | ||||
| // ZynAddSubFX | // ZynAddSubFX | ||||
| @@ -102,8 +102,8 @@ struct PluginListManager { | |||||
| //carla_register_native_plugin_StereoEnhancer(); // unfinished | //carla_register_native_plugin_StereoEnhancer(); // unfinished | ||||
| #endif | #endif | ||||
| // DISTRHO plugins (Qt) | |||||
| //carla_register_native_plugin_Notes(); // unfinished | |||||
| // DISTRHO plugins (PyQt) | |||||
| carla_register_native_plugin_Notes(); // unfinished | |||||
| #ifdef WANT_ZYNADDSUBFX | #ifdef WANT_ZYNADDSUBFX | ||||
| // ZynAddSubFX | // ZynAddSubFX | ||||
| @@ -164,12 +164,6 @@ void writeManifestFile() | |||||
| text += " ui:binary <carla-native" PLUGIN_EXT "> ;\n"; | text += " ui:binary <carla-native" PLUGIN_EXT "> ;\n"; | ||||
| text += " lv2:extensionData <" LV2_PROGRAMS__UIInterface "> ;\n"; | text += " lv2:extensionData <" LV2_PROGRAMS__UIInterface "> ;\n"; | ||||
| text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n"; | text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n"; | ||||
| text += "\n"; | |||||
| text += "<http://kxstudio.sf.net/carla#UIold>\n"; | |||||
| text += " a <" LV2_EXTERNAL_UI_DEPRECATED_URI "> ;\n"; | |||||
| text += " ui:binary <carla-native" PLUGIN_EXT "> ;\n"; | |||||
| text += " lv2:extensionData <" LV2_PROGRAMS__UIInterface "> ;\n"; | |||||
| text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n"; | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| // Write file now | // Write file now | ||||
| @@ -291,8 +285,7 @@ void writePluginFile(const PluginDescriptor* const pluginDesc) | |||||
| if (pluginDesc->hints & PLUGIN_HAS_GUI) | if (pluginDesc->hints & PLUGIN_HAS_GUI) | ||||
| { | { | ||||
| text += " ui:ui <http://kxstudio.sf.net/carla#UI> ,\n"; | |||||
| text += " <http://kxstudio.sf.net/carla#UIold> ;\n"; | |||||
| text += " ui:ui <http://kxstudio.sf.net/carla#UI> ;\n"; | |||||
| text += "\n"; | text += "\n"; | ||||
| } | } | ||||
| @@ -1321,23 +1321,7 @@ const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) | |||||
| /* extension_data */ lv2ui_extension_data | /* extension_data */ lv2ui_extension_data | ||||
| }; | }; | ||||
| static const LV2UI_Descriptor lv2UiDescOld = { | |||||
| /* URI */ "http://kxstudio.sf.net/carla#UIold", | |||||
| /* instantiate */ lv2ui_instantiate, | |||||
| /* cleanup */ lv2ui_cleanup, | |||||
| /* port_event */ lv2ui_port_event, | |||||
| /* extension_data */ lv2ui_extension_data | |||||
| }; | |||||
| switch (index) | |||||
| { | |||||
| case 0: | |||||
| return &lv2UiDesc; | |||||
| case 1: | |||||
| return &lv2UiDescOld; | |||||
| default: | |||||
| return nullptr; | |||||
| } | |||||
| return (index == 0) ? &lv2UiDesc : nullptr; | |||||
| } | } | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| @@ -1 +1 @@ | |||||
| ../../backend/resources/ | |||||
| ../../modules/carla_native/resources | |||||
| @@ -93,6 +93,7 @@ class ParamProgressBar(QProgressBar): | |||||
| QProgressBar.__init__(self, parent) | QProgressBar.__init__(self, parent) | ||||
| self.fLeftClickDown = False | self.fLeftClickDown = False | ||||
| self.fIsInteger = False | |||||
| self.fMinimum = 0.0 | self.fMinimum = 0.0 | ||||
| self.fMaximum = 1.0 | self.fMaximum = 1.0 | ||||
| @@ -163,6 +164,8 @@ class ParamProgressBar(QProgressBar): | |||||
| def paintEvent(self, event): | def paintEvent(self, event): | ||||
| if self.fTextCall is not None: | if self.fTextCall is not None: | ||||
| self.setFormat("%s %s %s" % (self.fPreLabel, self.fTextCall(), self.fLabel)) | self.setFormat("%s %s %s" % (self.fPreLabel, self.fTextCall(), self.fLabel)) | ||||
| elif self.fIsInteger: | |||||
| self.setFormat("%s %i %s" % (self.fPreLabel, int(self.fRealValue), self.fLabel)) | |||||
| else: | else: | ||||
| self.setFormat("%s %f %s" % (self.fPreLabel, self.fRealValue, self.fLabel)) | self.setFormat("%s %f %s" % (self.fPreLabel, self.fRealValue, self.fLabel)) | ||||
| @@ -245,6 +248,8 @@ class ParamSpinBox(QAbstractSpinBox): | |||||
| if self.fStepLarge < value: | if self.fStepLarge < value: | ||||
| self.fStepLarge = value | self.fStepLarge = value | ||||
| self.fBar.fIsInteger = bool(self.fStepSmall == 1.0) | |||||
| def setStepSmall(self, value): | def setStepSmall(self, value): | ||||
| if value == 0.0: | if value == 0.0: | ||||
| self.fStepSmall = 0.0001 | self.fStepSmall = 0.0001 | ||||
| @@ -253,6 +258,8 @@ class ParamSpinBox(QAbstractSpinBox): | |||||
| else: | else: | ||||
| self.fStepSmall = value | self.fStepSmall = value | ||||
| self.fBar.fIsInteger = bool(self.fStepSmall == 1.0) | |||||
| def setStepLarge(self, value): | def setStepLarge(self, value): | ||||
| if value == 0.0: | if value == 0.0: | ||||
| self.fStepLarge = 0.1 | self.fStepLarge = 0.1 | ||||